from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Any

from niannian.memory.fragment import FragmentValidationError, normalize_fragment
from niannian.memory.types import RecordResult
from niannian.schemas.models import Atom


class RecordService:
    """① 记忆 Record — Fragment → 小忆 atom。"""

    def __init__(self, orchestrator, left, cerebellum, config: dict[str, Any]) -> None:
        self._orchestrator = orchestrator
        self._left = left
        self._cerebellum = cerebellum
        self._config = config
        self._dedup_hours = int(config.get("memory", {}).get("dedup_window_hours", 24))
        self._max_batch = int(config.get("fragments", {}).get("max_batch_size", 100))

    def record(self, fragment: dict[str, Any]) -> RecordResult:
        normalized = normalize_fragment(fragment)
        dedup_key = normalized.get("dedup_key")
        if dedup_key:
            existing = self._find_dedup_atom(dedup_key, normalized["project"])
            if existing is not None:
                return RecordResult(
                    atom_id=existing.id,
                    thread_id=normalized.get("thread_id"),
                    deduped=True,
                    atom=existing.to_dict(),
                )

        atom, _trace = self._orchestrator.ingest(
            normalized["text"],
            tags=normalized["tags"],
            source=normalized["source"],
            project=normalized["project"],
            meta=normalized["meta"],
        )
        recent = self._left.list_atoms(project=normalized["project"], limit=50)
        touched = self._cerebellum.compile_from_atoms(recent)
        return RecordResult(
            atom_id=atom.id,
            thread_id=normalized.get("thread_id"),
            wiki_touched=touched,
            atom=atom.to_dict(),
        )

    def record_batch(self, fragments: list[dict[str, Any]]) -> list[RecordResult]:
        if len(fragments) > self._max_batch:
            raise FragmentValidationError(f"batch size exceeds max {self._max_batch}")
        return [self.record(f) for f in fragments]

    def _find_dedup_atom(self, dedup_key: str, project: str) -> Atom | None:
        cutoff = datetime.now(timezone.utc) - timedelta(hours=self._dedup_hours)
        for atom in reversed(self._left.list_atoms(project=project, limit=2000)):
            if atom.meta.get("dedup_key") != dedup_key:
                continue
            try:
                created = datetime.fromisoformat(atom.created_at)
            except ValueError:
                continue
            if created.tzinfo is None:
                created = created.replace(tzinfo=timezone.utc)
            if created >= cutoff:
                return atom
        return None
