"""文史自学配置与状态 — cultural_learning.yaml + cultural_state.yaml。"""

from __future__ import annotations

from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from uuid import uuid4

import yaml


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


def _config_path(config: dict[str, Any], home: Path | None = None) -> Path:
    soul = config.get("soul") or {}
    raw = soul.get("cultural_learning") or "config/soul/cultural_learning.yaml"
    path = Path(raw)
    if path.is_file():
        return path
    if home is not None:
        candidate = home / raw
        if candidate.is_file():
            return candidate
    return path


def _default_config() -> dict[str, Any]:
    return {
        "enabled": False,
        "auto_web_search": False,
        "schedule": {"mode": "dmn_window", "max_runs_per_day": 3},
        "corpus": {"domains": [], "curated_references": []},
        "ingest": {
            "trust_tier": "curated",
            "reality_anchor": "multi_source",
            "subject_eligible": True,
            "memory_scope": "subject",
        },
        "synthesize": {"primary_target": "SL4", "sl5_touch": "forbidden"},
    }


class CulturalLearningService:
    """读 yaml 配置 + 持久化 subject 侧开关与批次。"""

    def __init__(self, store, config: dict[str, Any], *, home: Path | None = None) -> None:
        self._store = store
        self._config = config
        self._home = home
        self._yaml_path = _config_path(config, home=home)
        self._state_path = store.subject_root / "cultural_state.yaml"

    def load_yaml_config(self) -> dict[str, Any]:
        if not self._yaml_path.is_file():
            return _default_config()
        with self._yaml_path.open(encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        if not isinstance(data, dict):
            return _default_config()
        return data

    def load_state(self) -> dict[str, Any]:
        if not self._state_path.is_file():
            return self._empty_state()
        with self._state_path.open(encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        if not isinstance(data, dict):
            return self._empty_state()
        data.setdefault("enabled", False)
        data.setdefault("auto_learning", data.get("auto_web_search", False))
        data.setdefault("personality_bootstrap_complete", False)
        data.setdefault("allow_values_update", False)
        data.setdefault("custom_references", [])
        data.setdefault("batches", [])
        data.setdefault("runs_today", 0)
        data.setdefault("runs_day", _today_key())
        return data

    def save_state(self, state: dict[str, Any]) -> None:
        self._state_path.parent.mkdir(parents=True, exist_ok=True)
        self._state_path.write_text(
            yaml.safe_dump(state, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

    @staticmethod
    def _empty_state() -> dict[str, Any]:
        return {
            "enabled": False,
            "auto_learning": False,
            "personality_bootstrap_complete": False,
            "allow_values_update": False,
            "custom_references": [],
            "batches": [],
            "runs_today": 0,
            "runs_day": _today_key(),
            "last_batch_at": None,
        }

    def is_bootstrap_complete(self) -> bool:
        return bool(self.load_state().get("personality_bootstrap_complete"))

    def values_update_allowed(self) -> bool:
        state = self.load_state()
        if not state.get("allow_values_update"):
            return False
        synth = self.load_yaml_config().get("synthesize") or {}
        return str(synth.get("values_touch") or "owner_gate") == "owner_gate"

    def patch_settings(
        self,
        *,
        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, Any] | None = None,
    ) -> dict[str, Any]:
        state = self.load_state()
        if enabled is not None:
            state["enabled"] = bool(enabled)
        auto_val = auto_learning if auto_learning is not None else auto_web_search
        if auto_val is not None:
            state["auto_learning"] = bool(auto_val)
            state["auto_web_search"] = bool(auto_val)
        if personality_bootstrap_complete is not None:
            state["personality_bootstrap_complete"] = bool(personality_bootstrap_complete)
        if allow_values_update is not None:
            state["allow_values_update"] = bool(allow_values_update)
        if append_reference:
            refs = list(state.get("custom_references") or [])
            refs.append(
                {
                    "id": f"custom_{uuid4().hex[:6]}",
                    "name": str(append_reference.get("name") or "主人追加"),
                    "search_queries": [str(append_reference.get("query") or append_reference.get("name") or "")],
                    "added_at": _now_iso(),
                }
            )
            state["custom_references"] = refs[-20:]
        self.save_state(state)
        return self.to_api_dict()

    def can_run_batch(self) -> tuple[bool, str]:
        cfg = self.load_yaml_config()
        state = self.load_state()
        if not state.get("enabled"):
            return False, "disabled"
        self._reset_runs_if_new_day(state)
        max_runs = int((cfg.get("schedule") or {}).get("max_runs_per_day", 3))
        if int(state.get("runs_today") or 0) >= max_runs:
            return False, "daily_limit"
        return True, "ok"

    def record_batch(self, batch: dict[str, Any]) -> None:
        state = self.load_state()
        self._reset_runs_if_new_day(state)
        batches = list(state.get("batches") or [])
        batches.append(batch)
        state["batches"] = batches[-30:]
        state["runs_today"] = int(state.get("runs_today") or 0) + 1
        state["last_batch_at"] = batch.get("at") or _now_iso()
        self.save_state(state)

    def to_api_dict(self) -> dict[str, Any]:
        cfg = self.load_yaml_config()
        state = self.load_state()
        domains = (cfg.get("corpus") or {}).get("domains") or []
        refs = list((cfg.get("corpus") or {}).get("curated_references") or [])
        refs.extend(state.get("custom_references") or [])
        batches = list(state.get("batches") or [])
        recent = list(reversed(batches[-10:]))
        syntheses = [
            {
                "domain": b.get("domain"),
                "title": b.get("title"),
                "atom_ids": b.get("atom_ids") or [],
                "at": b.get("at"),
                "status": b.get("status"),
            }
            for b in recent
            if isinstance(b, dict)
        ]
        return {
            "enabled": bool(state.get("enabled")),
            "auto_learning": bool(state.get("auto_learning", state.get("auto_web_search"))),
            "auto_web_search": bool(state.get("auto_learning", state.get("auto_web_search"))),
            "personality_bootstrap_complete": bool(state.get("personality_bootstrap_complete")),
            "allow_values_update": bool(state.get("allow_values_update")),
            "schedule": cfg.get("schedule") or {},
            "schedule_label": "人格启动期 · 不限时段",
            "domains": [
                {"id": d.get("id"), "label": d.get("label")}
                for d in domains
                if isinstance(d, dict)
            ],
            "curated_references": [
                {"id": r.get("id"), "name": r.get("name")}
                for r in refs
                if isinstance(r, dict)
            ],
            "last_batch_at": state.get("last_batch_at"),
            "recent_batches": recent,
            "recent_syntheses": syntheses,
            "runs_today": int(state.get("runs_today") or 0),
        }

    def pick_search_task(self, *, domain_id: str | None = None) -> dict[str, Any]:
        cfg = self.load_yaml_config()
        state = self.load_state()
        domains = [d for d in ((cfg.get("corpus") or {}).get("domains") or []) if isinstance(d, dict)]
        if domain_id:
            domains = [d for d in domains if d.get("id") == domain_id] or domains
        if not domains:
            raise ValueError("no domains configured")
        batch_count = len(state.get("batches") or [])
        domain = domains[batch_count % len(domains)]
        refs = list((cfg.get("corpus") or {}).get("curated_references") or [])
        refs.extend(state.get("custom_references") or [])
        if not refs:
            raise ValueError("no curated references")
        ref = refs[batch_count % len(refs)]
        queries = [q for q in (ref.get("search_queries") or []) if isinstance(q, str) and q.strip()]
        if not queries:
            queries = [str(ref.get("name") or "文史自学")]
        query = queries[batch_count % len(queries)]
        return {
            "domain_id": domain.get("id"),
            "domain_label": domain.get("label") or domain.get("id"),
            "reference_id": ref.get("id"),
            "reference_name": ref.get("name"),
            "query": query,
        }

    @staticmethod
    def _reset_runs_if_new_day(state: dict[str, Any]) -> None:
        today = _today_key()
        if state.get("runs_day") != today:
            state["runs_day"] = today
            state["runs_today"] = 0


def _today_key() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%d")
