from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from niannian.bridge.pairing import PairingService
from niannian.brain.brainstem.orchestrator import Orchestrator
from niannian.brain.brainstem.track_registry import TrackRegistry
from niannian.brain.brainstem.write_queue import WriteQueue
from niannian.brain.dmn.idle import DMNRunner
from niannian.brain.executive.composer import ExecutiveComposer
from niannian.brain.executive.task_state import TaskStateStore
from niannian.brain.cerebellum.rerank import CerebellumRerank
from niannian.brain.cerebellum.wiki import CerebellumWiki
from niannian.brain.hippocampus.consolidate import HippocampusDream
from niannian.brain.left.store import LeftStore
from niannian.brain.right.reason import RightReason
from niannian.chat.pipeline import ChatPipeline
from niannian.chat.session import SessionStore
from niannian.agents.infrastructure import build_agent_infrastructure
from niannian.brain.state.store import CognitiveStateStore
from niannian.memory.meta import parse_atom_meta
from niannian.object.tenant import ObjectTenantStore
from niannian.soul.reflect_loop import ReflectLoop
from niannian.soul.reflection_scorer import TriReviewScorer
from niannian.soul.subject_store import SubjectStateStore
from niannian.config import ensure_data_dirs, load_agents_config, load_config, load_providers_config, resolve_data_home
from niannian.core.schemas.models import AssistResult, Atom, QueryResult
from niannian.device.gateway import DeviceGateway
from niannian.llm.api_router import ApiRouter
from niannian.token.usage_store import TokenUsageStore
from niannian.memory.fragment import build_fragment
from niannian.memory.record import RecordService
from niannian.memory.recall import RecallEngine
from niannian.vault.store import VaultStore
from niannian.memory.reflect import ReflectService
from niannian.memory.disclosure import RecallContext
from niannian.memory.types import RecallPack, RecordResult, ReflectResult
from niannian.skills.export import resolve_skills_root
from niannian.skills.loader import SkillLoader
from niannian.storage.local import LocalMemoryStore
from niannian.storage.sync.agent import SyncAgent


class NianNian:
    """
    念念 — 外置记忆大脑对外唯一入口。

    NovelStudio / Codex / 任意 Python 项目：

        from niannian import NianNian
        nn = NianNian(project="novelstudio")
        nn.ingest("用户偏好：章节标题要短", tags=["写作", "偏好"])
        pack = nn.assist("续写本章", context="当前在写第3章")
        # pack.context_block → 注入 LLM system / user 前缀
    """

    def __init__(
        self,
        *,
        home: str | Path | None = None,
        project: str = "global",
        config_path: Path | None = None,
    ) -> None:
        self._config_path = config_path
        self._config = load_config(config_path)
        self._home = resolve_data_home(home)
        self._dirs = ensure_data_dirs(self._home)
        self.project = project
        show_trace = bool(self._config.get("trace", {}).get("show_internal_names", True))

        from niannian.llm.config_store import bootstrap_llm_config_from_files

        bootstrap_llm_config_from_files()
        providers_cfg = load_providers_config()
        self._store = LocalMemoryStore(self._home, config=self._config, providers=providers_cfg)
        agents_cfg = load_agents_config()
        self._agents = build_agent_infrastructure(agents_cfg)
        self._cognitive_state = CognitiveStateStore(self._home)
        self._object_store = ObjectTenantStore(self._home, user_id="pinn")
        self._subject_store = SubjectStateStore(self._home)
        self._task_state = TaskStateStore(self._home)
        self._executive = ExecutiveComposer()
        self._sync = SyncAgent(self._store, config=self._config)
        self._write_queue = WriteQueue()
        self._track_registry = TrackRegistry()
        self._left = LeftStore(self._dirs["left"], store=self._store, write_queue=self._write_queue)
        self._cerebellum = CerebellumWiki(self._dirs["cerebellum"], store=self._store)
        self._hippocampus = HippocampusDream(self._dirs["hippocampus"], store=self._store)
        self._right = RightReason()
        self._router = ApiRouter(
            self._config,
            providers_cfg,
            show_trace=show_trace,
        )
        self._token_usage = TokenUsageStore(self._home, self._config)
        self._router.llm.set_usage_store(self._token_usage)
        self._reflect_loop = ReflectLoop(
            self._subject_store,
            scorer=TriReviewScorer(llm=self._router.llm, config=self._config),
            config=self._config,
        )
        self._orchestrator = Orchestrator(
            self._left,
            self._cerebellum,
            self._hippocampus,
            self._right,
            self._router,
            show_trace=show_trace,
            config=self._config,
        )
        self._recall_engine = RecallEngine(
            self._left,
            self._cerebellum,
            self._hippocampus,
            self._right,
            self._config,
            rerank=CerebellumRerank(self._router.llm),
            vault_store=VaultStore(self._home, manifest=getattr(self._store, "manifest", None), config=self._config),
        )
        skills_root = resolve_skills_root(self._home)
        self._skills = SkillLoader(skills_root)
        audit_dir = self._home / "device"
        self._device = DeviceGateway(self._config, audit_dir=audit_dir)
        bridge_cfg = self._config.get("bridge", {})
        self._bridge_pairing = PairingService(ttl_minutes=int(bridge_cfg.get("pairing_ttl_minutes", 10)))
        self._session_store = SessionStore()
        self._chat = ChatPipeline(
            self._orchestrator,
            self._right,
            self._router,
            self._config,
            self._session_store,
            recall_engine=self._recall_engine,
            skills_loader=self._skills,
            device_gateway=self._device,
            memory_store=self._store,
            task_state=self._task_state,
            executive=self._executive,
            track_registry=self._track_registry,
            subject_store=self._cognitive_state,
            object_store=self._object_store,
        )
        self._record = RecordService(self._orchestrator, self._left, self._cerebellum, self._config)
        self._reflect = ReflectService(self._orchestrator, self._left, self._hippocampus, self._config)
        self._maybe_sync_on_launch()

    def _maybe_sync_on_launch(self) -> None:
        runtime = self._config.get("runtime") or {}
        mac_cfg = runtime.get("mac") or {}
        if mac_cfg.get("sync_agent") == "on_demand":
            self._sync.on_app_launch()

    def reload_llm_config(self) -> None:
        """热加载 providers.local 与 api 脑区路由。"""
        from niannian.llm.config_store import bootstrap_llm_config_from_files, sync_brain_api_into_config

        bootstrap_llm_config_from_files()
        sync_brain_api_into_config(self._config, self._config_path)
        providers_cfg = load_providers_config()
        self._router.reload_llm(self._config, providers_cfg)
        self._router.llm.set_usage_store(self._token_usage)

    @property
    def chat(self) -> ChatPipeline:
        return self._chat

    @property
    def home(self) -> Path:
        return self._home

    @property
    def skills(self) -> SkillLoader:
        return self._skills

    @property
    def device(self) -> DeviceGateway:
        return self._device

    @property
    def bridge_pairing(self) -> PairingService:
        return self._bridge_pairing

    @property
    def token_usage(self) -> TokenUsageStore:
        return self._token_usage

    def ingest(
        self,
        text: str,
        *,
        tags: list[str] | None = None,
        source: str = "manual",
        project: str | None = None,
        meta: dict[str, Any] | None = None,
    ) -> Atom:
        atom, _trace = self._orchestrator.ingest(
            text,
            tags=tags,
            source=source,
            project=project or self.project,
            meta=meta,
        )
        return atom

    def _ensure_fresh_for_read(self) -> None:
        from niannian.storage.sync.hydrate import ensure_fresh_for_read

        ensure_fresh_for_read(self._store, config=self._config)

    def query(self, question: str, *, project: str | None = None) -> QueryResult:
        self._ensure_fresh_for_read()
        return self._recall_engine.query(
            question,
            project=project or self.project,
            llm=self._router.llm,
        )

    def assist(
        self,
        task: str,
        *,
        context: str = "",
        project: str | None = None,
    ) -> AssistResult:
        self._ensure_fresh_for_read()
        return self._recall_engine.assist(task, context=context, project=project or self.project)

    def record(
        self,
        summary: str = "",
        *,
        kind: str = "note",
        scope: dict[str, Any] | None = None,
        refs: list[str] | None = None,
        tags: list[str] | None = None,
        project: str | None = None,
        subtype: str | None = None,
        thread_id: str | None = None,
        fragment: dict[str, Any] | None = None,
    ) -> RecordResult:
        """MRRR 记录 — Fragment/2 落小忆。"""
        if fragment is not None:
            return self._record.record(fragment)
        if not summary:
            raise ValueError("summary or fragment is required")
        frag = build_fragment(
            summary,
            kind=kind,
            project=project or self.project,
            scope=scope,
            refs=refs,
            tags=tags,
            subtype=subtype,
            thread_id=thread_id,
        )
        return self._record.record(frag)

    def recall(
        self,
        task: str = "",
        *,
        context: str = "",
        scope: dict[str, Any] | None = None,
        policy: str | None = None,
        project: str | None = None,
        limit: int | None = None,
        return_format: str = "pack",
        filters: dict[str, Any] | None = None,
        recall_context: RecallContext | None = None,
    ) -> RecallPack | list[dict[str, Any]] | str:
        """MRRR 回忆 — 返回 RecallPack（或 wiki 兼容 list）。"""
        self._ensure_fresh_for_read()
        if policy is None and scope is None and limit is not None and return_format == "pack":
            return self.recall_wiki(task, limit=limit, project=project)
        result = self._recall_engine.recall(
            task,
            context=context,
            scope=scope,
            policy=policy,
            project=project or self.project,
            return_format=return_format,
            filters=filters,
            recall_context=recall_context,
        )
        return result

    def recall_wiki(self, query: str, *, limit: int = 5, project: str | None = None) -> list[dict[str, Any]]:
        """v0.1 兼容 — 小织 Wiki 关键词召回。"""
        self._ensure_fresh_for_read()
        return self._recall_engine.recall_wiki(query, project=project or self.project, limit=limit)

    def reflect(
        self,
        *,
        scope: dict[str, Any] | None = None,
        project: str | None = None,
    ) -> ReflectResult:
        """MRRR 反思 — thread 摘要 + dream。"""
        return self._reflect.reflect(scope=scope, project=project or self.project)

    def dream(self, *, project: str | None = None) -> dict[str, Any]:
        proj = project or self.project
        stats = self._orchestrator.dream(project=proj)
        subject_n = int(stats.get("subject_dream") or 0)
        object_n = int(stats.get("object_dream") or 0)
        if subject_n or object_n:
            from niannian.soul.firewall import firewall_trace_line

            stats.setdefault("trace", [])
            if isinstance(stats["trace"], list):
                stats["trace"].append(
                    firewall_trace_line(f"subject_dream {subject_n} · object_dream {object_n}")
                )
        atoms = self._left.list_atoms(project=proj, limit=500)
        subject_ids = [
            a.id for a in atoms if parse_atom_meta(a.meta if isinstance(a.meta, dict) else None).memory_scope == "subject"
        ]
        summaries = self._hippocampus.store.read_summaries()
        dmn_stats = DMNRunner(self._config).after_dream(
            self._cognitive_state,
            subject_atom_ids=subject_ids,
        )
        if isinstance(dmn_stats.get("trace"), list):
            stats.setdefault("trace", [])
            if isinstance(stats["trace"], list):
                stats["trace"].extend(dmn_stats["trace"])
        stats["dmn"] = dmn_stats
        stats["reflection"] = self._reflect_loop.after_dream(summaries, subject_atom_ids=subject_ids)
        return stats

    def sync_status(self, *, probe_tier: bool = False) -> dict[str, Any]:
        return self._sync.status(probe_tier=probe_tier).to_dict()

    def sync_push(self, *, force: bool = False) -> dict[str, Any]:
        result = self._sync.push(force=force).to_dict()
        active = self.upload_progress_active()
        if active:
            result["upload_job_id"] = active.get("job_id")
        return result

    def sync_pull(self) -> dict[str, Any]:
        return self._sync.pull().to_dict()

    def upload_progress_active(self) -> dict[str, Any]:
        from niannian.storage.upload_checkpoint import make_upload_trackers

        progress, _ = make_upload_trackers(self._home, self._config)
        active = progress.get_active()
        return active.to_dict() if active else {}

    def upload_progress_job(self, job_id: str) -> dict[str, Any] | None:
        from niannian.storage.upload_checkpoint import make_upload_trackers

        progress, _ = make_upload_trackers(self._home, self._config)
        job = progress.get_job(job_id)
        return job.to_dict() if job else None

    def upload_progress_recent(self, *, limit: int = 10) -> list[dict[str, Any]]:
        from niannian.storage.upload_checkpoint import make_upload_trackers

        progress, _ = make_upload_trackers(self._home, self._config)
        return [job.to_dict() for job in progress.list_recent(limit=limit)]

    def upload_checkpoints_pending(self) -> int:
        from niannian.storage.upload_checkpoint import make_upload_trackers

        _, checkpoint = make_upload_trackers(self._home, self._config)
        return checkpoint.count_pending()

    def health(self) -> dict[str, Any]:
        sync = self.sync_status(probe_tier=False)
        runtime_cfg = self._config.get("runtime") or {}
        mac_cfg = runtime_cfg.get("mac") or {}
        mode = os.environ.get("NIANNIAN_MODE", runtime_cfg.get("mode", "auto"))
        atoms = self._left.list_atoms(limit=1)
        tier_block = sync.get("tier", {})
        credentials: dict[str, Any] = {}
        from niannian.storage.cloud_mirror import read_mirror_state
        from niannian.storage.cloud_tier import CloudTierRouter
        from niannian.storage.cloud_auth.manager import CloudCredentialManager
        from niannian.storage.feimao_probe import FeimaoReachability

        cloud = self._store.cloud
        mirror_state = read_mirror_state(self._home)
        feimao_reach: dict[str, Any] | None = None
        if isinstance(cloud, CloudTierRouter):
            feimao_ps = (cloud.tier_state.get("providers") or {}).get("feimao") or {}
            if feimao_ps.get("reachability"):
                feimao_reach = FeimaoReachability(
                    status=str(feimao_ps.get("reachability")),
                    hint=feimao_ps.get("reachability_hint"),
                    error_class=feimao_ps.get("last_error_class"),
                ).to_dict()
        mgr = CloudCredentialManager(self._home, config=self._config)
        credentials = mgr.credentials_health(feimao_reachability=feimao_reach, mirror_state=mirror_state)
        from niannian.storage.cloud_base import resolve_remote_root

        cloud_remote_root = resolve_remote_root(self._config)
        from niannian.storage.reconcile_report import ReconcileReporter

        reconcile = ReconcileReporter(self._home, self._config).health_summary()
        from niannian.storage.ops_error_log import OpsErrorLog

        ops_errors = OpsErrorLog(self._home, config=self._config).summary()
        status = "degraded" if sync.get("degraded") else "ok"
        active_upload = self.upload_progress_active()
        checkpoints_pending = self.upload_checkpoints_pending()
        upload_health: dict[str, Any] = {"checkpoints_pending": checkpoints_pending}
        if active_upload:
            upload_health["active"] = {
                "job_id": active_upload.get("job_id"),
                "percent": active_upload.get("percent"),
                "provider": active_upload.get("provider"),
            }
        return {
            "status": status,
            "home": str(self._home),
            "project": self.project,
            "has_atoms": bool(atoms),
            "storage": {
                "primary_mode": sync.get("primary_mode", "mirror"),
                "realtime": sync.get("realtime", "quark"),
                "clouds": sync.get("backends", {}),
                "cloud": {"remote_root": cloud_remote_root},
                "manifest_version": sync.get("version", 0),
                "last_dream_at": sync.get("last_dream_at"),
                "last_archive_at": sync.get("last_archive_at"),
                "backup": sync.get("backup", {}),
                "tier": tier_block,
                "credentials": credentials,
                "reconcile": reconcile,
                "archive": sync.get("archive", {}),
            },
            "reconcile": reconcile,
            "tier": {
                "recent_alerts": tier_block.get("recent_alerts", []),
                "pending_uploads": tier_block.get("pending_uploads", 0),
                "queue_path": tier_block.get("queue_path", "sync/pending_uploads.jsonl"),
                "active_role": tier_block.get("active_role"),
            },
            "ops_errors": ops_errors,
            "upload": upload_health,
            "runtime": {
                "mode": mode,
                "dream_deferred": bool(mac_cfg.get("defer_dream_to_docker")),
            },
            "cognitive": {
                "planes": 14,
                "state": self._cognitive_state.health_summary(),
            },
            "executive": self._task_state.health_summary(),
            "tracks": {
                **self._track_registry.health_summary(),
                "write_queue_depth": self._write_queue.depth,
            },
            "agents": self._agents.health_summary(),
        }

    def list_atoms(self, *, project: str | None = None, limit: int = 500) -> list[Atom]:
        return self._left.list_atoms(project=project or self.project, limit=limit)
