"""Core 纠错对话 — POST /v1/core/correct 管线。"""

from __future__ import annotations

import json
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Literal

from niannian.brain.left.store import LeftStore
from niannian.soul.assembly import build_values_gate
from niannian.soul.baseline import BaselineManager
from niannian.soul.drift import DriftDetector
from niannian.soul.evolution_log import append_evolution
from niannian.soul.reflect_loop import ReflectLoop
from niannian.soul.reflection_rules import rules_precheck
from niannian.soul.subject_store import SubjectStateStore

CorrectAction = Literal[
    "auto",
    "restore_baseline",
    "mark_hypothesis",
    "patch_narrative",
    "promote_subject",
]
CoreRole = Literal["owner", "authorized_corrector"]


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


class CorrectForbidden(Exception):
    def __init__(self, code: str, message: str) -> None:
        self.code = code
        self.message = message
        super().__init__(message)


class CorrectValidationError(Exception):
    def __init__(self, message: str) -> None:
        self.message = message
        super().__init__(message)


class CorrectionThreadStore:
    def __init__(self, subject_root: Path) -> None:
        self._path = subject_root / "correction_thread.jsonl"

    def append(self, role: str, content: str, *, meta: dict[str, Any] | None = None) -> dict[str, Any]:
        row = {
            "role": role,
            "content": content,
            "at": _now_iso(),
            "meta": meta or {},
        }
        self._path.parent.mkdir(parents=True, exist_ok=True)
        with self._path.open("a", encoding="utf-8") as f:
            f.write(json.dumps(row, ensure_ascii=False) + "\n")
        return row

    def tail(self, limit: int = 10) -> list[dict[str, Any]]:
        if not self._path.is_file():
            return []
        lines = self._path.read_text(encoding="utf-8").splitlines()
        rows: list[dict[str, Any]] = []
        for line in reversed(lines):
            line = line.strip()
            if not line:
                continue
            try:
                row = json.loads(line)
            except json.JSONDecodeError:
                continue
            if isinstance(row, dict):
                rows.append(row)
            if len(rows) >= limit:
                break
        return list(reversed(rows))


class CoreCorrectService:
    def __init__(
        self,
        store: SubjectStateStore,
        left: LeftStore,
        *,
        config: dict[str, Any] | None = None,
    ) -> None:
        self._store = store
        self._left = left
        self._config = config or {}
        self._baseline = BaselineManager(store)
        self._drift = DriftDetector(store, self._baseline)
        self._reflect = ReflectLoop(store, config=self._config)
        self._thread = CorrectionThreadStore(store.subject_root)

    def thread_tail(self, limit: int = 10) -> list[dict[str, Any]]:
        return self._thread.tail(limit)

    def correct(
        self,
        message: str,
        *,
        action: CorrectAction = "auto",
        role: CoreRole = "owner",
        target_atom_id: str | None = None,
        snapshot_id: str | None = None,
    ) -> dict[str, Any]:
        text = str(message or "").strip()
        if not text:
            raise CorrectValidationError("message 不能为空")

        if _guest_sl5_pollution(text):
            raise CorrectForbidden("guest_sl5_forbidden", "不得以 guest 数据为依据修改三观")

        resolved = self._resolve_action(action, text)
        if resolved == "restore_baseline" and role != "owner":
            raise CorrectForbidden("restore_forbidden", "仅 owner 可恢复基线")
        if resolved == "promote_subject" and role != "owner":
            raise CorrectForbidden("promote_forbidden", "仅 owner 可 promote guest 记忆")

        self._thread.append("user", text, meta={"action": action, "resolved": resolved})

        actions_taken: list[dict[str, Any]] = []
        reply = ""

        if resolved == "restore_baseline":
            snap_id = snapshot_id or self._latest_snapshot_id()
            if not snap_id:
                raise CorrectValidationError("无可用 SL8 基线快照，请先同步为纠偏基线")
            self._baseline.restore(snap_id)
            actions_taken.append({"type": "restore_baseline", "snapshot_id": snap_id})
            reply = f"已恢复 SL8 基线快照 {snap_id}（identity + values）。"
            self._append_evolution("correct_restore_baseline", snapshot_id=snap_id)

        elif resolved == "mark_hypothesis":
            atom_id = target_atom_id or _extract_atom_id(text)
            if not atom_id:
                raise CorrectValidationError("mark_hypothesis 需要 target_atom_id")
            atom = self._left.patch_atom_meta(
                atom_id,
                {"provenance": {"reality_anchor": "marked_hypothesis"}},
            )
            if atom is None:
                raise CorrectValidationError(f"atom 不存在: {atom_id}")
            actions_taken.append({"type": "mark_hypothesis", "atom_id": atom_id})
            reply = f"已将 atom {atom_id} 标为 marked_hypothesis（待验证）。"
            self._append_evolution("correct_mark_hypothesis", atom_id=atom_id)

        elif resolved == "promote_subject":
            atom_id = target_atom_id or _extract_atom_id(text)
            if not atom_id:
                raise CorrectValidationError("promote_subject 需要 target_atom_id")
            atom = self._left.get(atom_id)
            if atom is None:
                raise CorrectValidationError(f"atom 不存在: {atom_id}")
            tier = str((atom.meta or {}).get("provenance", {}).get("trust_tier") or "")
            if tier != "guest_reference":
                raise CorrectValidationError("仅 guest_reference atom 可 promote")
            updated = self._left.patch_atom_meta(
                atom_id,
                {
                    "subject_eligible": True,
                    "provenance": {
                        "trust_tier": "guest_reference",
                        "reality_anchor": "pinn_confirmed",
                    },
                },
            )
            if updated is None:
                raise CorrectValidationError(f"atom 不存在: {atom_id}")
            actions_taken.append({"type": "promote_subject", "atom_id": atom_id})
            reply = f"已将 guest atom {atom_id} 晋升为主体可参考（pinn_confirmed）。"
            self._append_evolution("promote_subject", atom_id=atom_id)

        elif resolved == "patch_narrative":
            values = self._store.load_values()
            seat_a, doubts = rules_precheck(text, values=values)
            if not seat_a:
                raise CorrectForbidden("sl5_conflict", f"提案与三观冲突：{doubts}")
            proposal = text if len(text) > 12 else f"主人纠错叙事：{text}"
            tid = self._reflect._panel.submit_proposal(proposal, kind="narrative_line")
            scored = self._reflect._scorer.score(proposal, kind="narrative_line", values=values)
            for tr in ("core_correct", "dream", "manual"):
                self._reflect._panel.run_round(
                    tr,
                    tid,
                    seat_a_pass=scored.seat_a_pass,
                    seat_b_score=max(scored.seat_b_score, 85.0),
                    seat_c_score=max(scored.seat_c_score, 85.0),
                )
            applied = self._reflect.apply_adopted()
            actions_taken.append(
                {"type": "patch_narrative", "thought_id": tid, "sl4_applied": applied}
            )
            reply = (
                f"叙事提案已处理（thought {tid}），SL4 写入 {applied} 条。"
                if applied
                else f"叙事提案 {tid} 已提交，暂未 adopt。"
            )
            self._append_evolution("correct_patch_narrative", thought_id=tid, applied=applied)

        drift = self._drift.check(record_audit=resolved in ("restore_baseline", "patch_narrative"))
        self._thread.append(
            "assistant",
            reply,
            meta={"resolved_action": resolved, "actions": actions_taken},
        )
        return {
            "reply": reply,
            "resolved_action": resolved,
            "actions_taken": actions_taken,
            "drift": drift.to_dict(),
            "thread_tail": self._thread.tail(10),
        }

    def _latest_snapshot_id(self) -> str | None:
        rows = self._baseline.list_snapshots()
        return str(rows[0]["id"]) if rows else None

    @staticmethod
    def _resolve_action(action: CorrectAction, message: str) -> CorrectAction:
        if action != "auto":
            return action
        if re.search(r"恢复|基线|restore", message, re.I):
            return "restore_baseline"
        if re.search(r"假设|待验证|hypothesis", message, re.I):
            return "mark_hypothesis"
        if re.search(r"晋升|promote", message, re.I):
            return "promote_subject"
        if re.search(r"叙事|SL4|narrative", message, re.I):
            return "patch_narrative"
        return "patch_narrative"

    def _append_evolution(
        self,
        action: str,
        *,
        atom_id: str | None = None,
        snapshot_id: str | None = None,
        thought_id: str | None = None,
        applied: int | None = None,
    ) -> None:
        target: dict[str, Any] = {"kind": "atom" if atom_id else "sl", "id": atom_id or "subject"}
        detail: dict[str, Any] = {}
        if snapshot_id:
            target = {"kind": "sl", "id": snapshot_id}
        if thought_id:
            target = {"kind": "thought", "id": thought_id}
            detail["sl4_applied"] = applied
        append_evolution(
            self._store.subject_root / "evolution.jsonl",
            action=action,
            actor="owner",
            target=target,
            detail=detail or None,
            reviewer="core_correct",
        )

    def values_gate_summary(self) -> dict[str, Any]:
        return build_values_gate(self._store.load_values())


def _guest_sl5_pollution(text: str) -> bool:
    return bool(re.search(r"guest.*(三观|SL5|价值观)", text, re.I))


def _extract_atom_id(text: str) -> str | None:
    m = re.search(r"\b([0-9a-f]{8,12})\b", text, re.I)
    return m.group(1) if m else None
