from __future__ import annotations

from typing import TYPE_CHECKING, Any

from fastapi import APIRouter, Depends, Query, Request
from pydantic import BaseModel, Field

from niannian.cognitive.pathway_registry import PathwayRegistry
from niannian.cognitive.plane_registry import PlaneRegistry
from niannian.cognitive.persona_registry import persona_registry_for_user
from niannian.cognitive.planes import Regime
from niannian.memory.disclosure import RecallContext
from niannian.core.correct_service import (
    CoreCorrectService,
    CorrectForbidden,
    CorrectValidationError,
)
from niannian.core.mind_service import CoreMindService
from niannian.core.organs_service import organs_api_dict
from niannian.http.core_auth import require_baseline_owner, require_core_access, require_core_docker_user
from niannian.object.tenant import ObjectTenantStore
from niannian.users.persona_sync import sync_persona_to_cloud
from niannian.soul.cultural_learning import CulturalLearningService
from niannian.soul.cultural_runner import CulturalRunner, build_search_provider
from niannian.soul.reflect_loop import ReflectLoop
from niannian.soul.search_providers import build_search_provider_from_yaml
from niannian.soul.web_learning import WebLearningConfigError, WebLearningService
from niannian.soul.web_learning_runner import WebLearningRunner
from niannian.vault.store import VaultStore

if TYPE_CHECKING:
    from niannian.client import NianNian


class BaselineSnapshotBody(BaseModel):
    label: str | None = None


class PackPatchBody(BaseModel):
    active: list[str] = Field(default_factory=list)
    skip: list[str] = Field(default_factory=list)


class PlanesPreviewBody(BaseModel):
    text: str = ""
    regime: Regime = "companion"
    pack: str | None = None


class PersonaPreviewBody(BaseModel):
    preset: str = "companion_warm"
    object_id: str | None = None
    regime: Regime = "companion"


class PersonaCloudSyncBody(BaseModel):
    include_objects: bool = False


class ObjectPersonaPatchBody(BaseModel):
    default_preset: str | None = None


class PathwayPreviewBody(BaseModel):
    text: str = "你好"
    regime: Regime | None = None
    object_id: str | None = None


class AtomBeliefPatchBody(BaseModel):
    belief_status: str


class CulturalLearningPatchBody(BaseModel):
    enabled: bool | None = None
    auto_learning: bool | None = None
    auto_web_search: bool | None = None
    personality_bootstrap_complete: bool | None = None
    allow_values_update: bool | None = None
    append_reference: dict[str, str] | None = None


class CulturalRunBatchBody(BaseModel):
    domain_id: str | None = None
    mock: bool = False


class WebLearningPatchBody(BaseModel):
    enabled: bool | None = None
    auto_learning: bool | None = None
    auto_web_search: bool | None = None
    append_query: dict[str, str] | None = None
    schedule: dict[str, Any] | None = None
    active_topic_ids: list[str] | None = None


class WebLearningTopicBody(BaseModel):
    label: str
    seed_queries: list[str] | None = None


class WebLearningRunBatchBody(BaseModel):
    topic_id: str | None = None
    query: str | None = None
    mock: bool = False


class CoreCorrectBody(BaseModel):
    message: str
    action: str = "auto"
    target_atom_id: str | None = None
    snapshot_id: str | None = None


class RecallDebugBody(BaseModel):
    query: str = ""
    policy: str = "assist_default"
    recall_scope: str = "subject"
    object_id: str | None = None
    pathway: Regime = "companion"
    rerank: bool | None = None
    project: str = "global"


def create_core_router(nn: NianNian) -> APIRouter:
    router = APIRouter(tags=["core"])
    service = CoreMindService(nn)
    planes_reg = PlaneRegistry(nn._config, home=nn._home)
    pathway_reg = PathwayRegistry(nn._config, home=nn._home)
    correct_svc = CoreCorrectService(nn._subject_store, nn._left, config=nn._config)

    @router.get("/v1/core/mind")
    def core_mind(_role: str = Depends(require_core_access)):
        return service.get_mind()

    @router.get("/v1/core/memories")
    def core_memories(
        _role: str = Depends(require_core_access),
        scope: str = Query(default="subject"),
        limit: int = Query(default=50, ge=1, le=100),
        cursor: str | None = Query(default=None),
        project: str | None = Query(default=None),
    ):
        return service.list_memories(scope=scope, limit=limit, cursor=cursor, project=project)

    @router.post("/v1/core/baseline/snapshot")
    def core_baseline_snapshot(
        payload: BaselineSnapshotBody,
        _role: str = Depends(require_baseline_owner),
    ):
        return service.snapshot_baseline(label=payload.label)

    @router.get("/v1/core/planes")
    def core_planes(_role: str = Depends(require_core_access)):
        return planes_reg.to_api_dict()

    @router.get("/v1/core/planes/packs/{name}")
    def core_get_pack(name: str, _role: str = Depends(require_core_access)):
        pack = planes_reg.get_pack(name)
        if pack is None:
            from fastapi import HTTPException

            raise HTTPException(status_code=404, detail=f"pack not found: {name}")
        return {"name": name, **pack}

    @router.patch("/v1/core/planes/packs/{name}")
    def core_patch_pack(name: str, payload: PackPatchBody, _role: str = Depends(require_core_access)):
        try:
            updated = planes_reg.update_pack(name, active=payload.active, skip=payload.skip)
        except KeyError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=404, detail=str(exc)) from exc
        except ValueError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=400, detail=str(exc)) from exc
        return {"name": name, **updated}

    @router.post("/v1/core/planes/preview")
    def core_planes_preview(payload: PlanesPreviewBody, _role: str = Depends(require_core_access)):
        return planes_reg.preview(text=payload.text, regime=payload.regime, pack=payload.pack)

    def _core_user_id(request: Request) -> str:
        return require_core_docker_user(request, config=nn._config, home=nn._home)

    @router.get("/v1/core/personas")
    def core_personas(request: Request):
        user_id = _core_user_id(request)
        reg = persona_registry_for_user(nn._home, user_id, nn._config)
        return reg.to_api_dict()

    @router.post("/v1/core/personas/preview")
    def core_personas_preview(payload: PersonaPreviewBody, request: Request):
        user_id = _core_user_id(request)
        reg = persona_registry_for_user(nn._home, user_id, nn._config)
        return reg.preview(preset=payload.preset, regime=payload.regime)

    @router.post("/v1/core/personas/cloud-sync")
    def core_personas_cloud_sync(request: Request, payload: PersonaCloudSyncBody | None = None):
        require_baseline_owner(request)
        user_id = _core_user_id(request)
        if user_id != "pinn":
            from fastapi import HTTPException

            raise HTTPException(
                status_code=403,
                detail={"code": "persona_sync_forbidden", "message": "persona cloud_sync is pinn-only"},
            )
        body = payload or PersonaCloudSyncBody()
        try:
            return sync_persona_to_cloud(
                nn._home,
                user_id,
                include_objects=body.include_objects,
            )
        except FileNotFoundError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=404, detail={"code": "persona_missing", "message": str(exc)}) from exc
        except ValueError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=403, detail={"code": "persona_sync_forbidden", "message": str(exc)}) from exc

    @router.get("/v1/core/objects/{object_id}/persona")
    def core_get_object_persona(object_id: str, request: Request):
        user_id = _core_user_id(request)
        store = ObjectTenantStore(nn._home, user_id=user_id)
        return store.load_persona(object_id)

    @router.patch("/v1/core/objects/{object_id}/persona")
    def core_patch_object_persona(
        object_id: str,
        payload: ObjectPersonaPatchBody,
        request: Request,
    ):
        user_id = _core_user_id(request)
        store = ObjectTenantStore(nn._home, user_id=user_id)
        try:
            return store.save_persona(object_id, {"default_preset": payload.default_preset})
        except ValueError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @router.get("/v1/core/pathways")
    def core_pathways(_role: str = Depends(require_core_access)):
        return pathway_reg.to_api_dict()

    @router.post("/v1/core/pathways/preview")
    def core_pathways_preview(payload: PathwayPreviewBody, _role: str = Depends(require_core_access)):
        return pathway_reg.preview(
            text=payload.text,
            regime=payload.regime,
            object_id=payload.object_id,
        )

    @router.get("/v1/core/organs")
    def core_organs(_role: str = Depends(require_core_access)):
        return organs_api_dict(nn)

    @router.get("/v1/core/executive")
    def core_executive(_role: str = Depends(require_baseline_owner)):
        return nn._task_state.to_executive_api()

    cultural_svc = CulturalLearningService(nn._subject_store, nn._config, home=nn._home)

    @router.get("/v1/core/cultural-learning")
    def core_cultural_learning(_role: str = Depends(require_core_access)):
        return cultural_svc.to_api_dict()

    @router.patch("/v1/core/cultural-learning")
    def core_patch_cultural_learning(
        payload: CulturalLearningPatchBody,
        _role: str = Depends(require_baseline_owner),
    ):
        return cultural_svc.patch_settings(
            enabled=payload.enabled,
            auto_learning=payload.auto_learning,
            auto_web_search=payload.auto_web_search,
            personality_bootstrap_complete=payload.personality_bootstrap_complete,
            allow_values_update=payload.allow_values_update,
            append_reference=payload.append_reference,
        )

    @router.post("/v1/core/cultural-learning/run-batch")
    def core_cultural_run_batch(
        payload: CulturalRunBatchBody,
        _role: str = Depends(require_baseline_owner),
    ):
        yaml_cfg = cultural_svc.load_yaml_config()
        if payload.mock:
            yaml_cfg = {**yaml_cfg, "mock_search": {**dict(yaml_cfg.get("mock_search") or {}), "enabled": True}}
        search = build_search_provider(yaml_cfg)
        loop = ReflectLoop(nn._subject_store, config=nn._config)
        vault_store = None
        vault_cfg = nn._config.get("vault") or {}
        if vault_cfg.get("enabled"):
            vault_store = VaultStore(
                nn._home,
                manifest=getattr(nn._store, "manifest", None),
                config=nn._config,
            )
        runner = CulturalRunner(
            cultural_svc,
            reflect_loop=loop,
            ingest_fn=nn.ingest,
            config=nn._config,
            vault_store=vault_store,
            memory_store=getattr(nn, "_store", None),
        )
        return runner.run_batch(search=search, domain_id=payload.domain_id, project=nn.project)

    web_svc = WebLearningService(nn._subject_store, nn._config, home=nn._home)

    @router.get("/v1/core/web-learning")
    def core_web_learning(_role: str = Depends(require_core_access)):
        return web_svc.to_api_dict()

    @router.patch("/v1/core/web-learning")
    def core_patch_web_learning(
        payload: WebLearningPatchBody,
        _role: str = Depends(require_baseline_owner),
    ):
        try:
            return web_svc.patch_settings(
                enabled=payload.enabled,
                auto_learning=payload.auto_learning,
                auto_web_search=payload.auto_web_search,
                append_query=payload.append_query,
                schedule=payload.schedule,
                active_topic_ids=payload.active_topic_ids,
            )
        except WebLearningConfigError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @router.put("/v1/core/web-learning/topics/{topic_id}")
    def core_upsert_web_topic(
        topic_id: str,
        payload: WebLearningTopicBody,
        _role: str = Depends(require_baseline_owner),
    ):
        try:
            return web_svc.upsert_topic(
                topic_id=topic_id,
                label=payload.label,
                seed_queries=payload.seed_queries,
            )
        except WebLearningConfigError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @router.delete("/v1/core/web-learning/topics/{topic_id}")
    def core_delete_web_topic(
        topic_id: str,
        _role: str = Depends(require_baseline_owner),
    ):
        try:
            return web_svc.delete_topic(topic_id)
        except WebLearningConfigError as exc:
            from fastapi import HTTPException

            raise HTTPException(status_code=400, detail=str(exc)) from exc

    @router.post("/v1/core/web-learning/run-batch")
    def core_web_run_batch(
        payload: WebLearningRunBatchBody,
        _role: str = Depends(require_baseline_owner),
    ):
        yaml_cfg = web_svc.load_yaml_config()
        if payload.mock:
            yaml_cfg = {**yaml_cfg, "mock_search": {**dict(yaml_cfg.get("mock_search") or {}), "enabled": True}}
        search = build_search_provider_from_yaml(yaml_cfg)
        loop = ReflectLoop(nn._subject_store, config=nn._config)
        runner = WebLearningRunner(
            web_svc,
            reflect_loop=loop,
            ingest_fn=nn.ingest,
            config=nn._config,
        )
        return runner.run_batch(
            search=search,
            topic_id=payload.topic_id,
            query=payload.query,
            project=nn.project,
            manual=True,
        )

    @router.post("/v1/core/recall/debug")
    def core_recall_debug(payload: RecallDebugBody, _role: str = Depends(require_core_access)):
        from fastapi import HTTPException

        if payload.recall_scope == "object" and not (payload.object_id and str(payload.object_id).strip()):
            raise HTTPException(status_code=400, detail={"message": "recall_scope=object 时须填 object_id"})
        if payload.recall_scope == "object":
            ctx = RecallContext(mode="object", object_id=str(payload.object_id).strip())
        else:
            ctx = RecallContext(mode="subject")

        filters: dict[str, object] | None = None
        if payload.rerank is not None:
            filters = {"rerank": {"enabled": payload.rerank}}

        result = nn.recall(
            payload.query,
            policy=payload.policy,
            project=payload.project,
            return_format="pack",
            filters=filters,
            recall_context=ctx,
        )
        if not hasattr(result, "to_dict"):
            raise HTTPException(status_code=500, detail={"message": "recall 未返回 RecallPack"})
        out = result.to_dict()
        preview = planes_reg.preview(text=payload.query, regime=payload.pathway, pack=payload.pathway)
        out["pathway_preview"] = preview
        return out

    @router.post("/v1/core/correct")
    def core_correct(payload: CoreCorrectBody, role: str = Depends(require_core_access)):
        from fastapi import HTTPException

        try:
            return correct_svc.correct(
                payload.message,
                action=payload.action,  # type: ignore[arg-type]
                role=role,  # type: ignore[arg-type]
                target_atom_id=payload.target_atom_id,
                snapshot_id=payload.snapshot_id,
            )
        except CorrectForbidden as exc:
            raise HTTPException(
                status_code=403,
                detail={"code": exc.code, "message": exc.message},
            ) from exc
        except CorrectValidationError as exc:
            raise HTTPException(status_code=400, detail={"message": exc.message}) from exc

    @router.patch("/v1/core/atoms/{atom_id}/belief")
    def core_patch_atom_belief(
        atom_id: str,
        payload: AtomBeliefPatchBody,
        _role: str = Depends(require_baseline_owner),
    ):
        from fastapi import HTTPException
        from niannian.memory.belief import InvalidBeliefStatusError, normalize_belief_status

        store = getattr(nn, "_store", None)
        if store is None:
            raise HTTPException(status_code=503, detail={"message": "memory store unavailable"})
        try:
            status = normalize_belief_status(payload.belief_status)
        except InvalidBeliefStatusError as exc:
            raise HTTPException(status_code=400, detail={"message": str(exc)}) from exc
        updated = store.patch_atom_meta(atom_id, {"belief_status": status})
        if updated is None:
            raise HTTPException(status_code=404, detail={"message": "atom not found"})
        return {"atom_id": updated.id, "belief_status": status, "meta": updated.meta}

    return router
