"""租户初始化包 — archive/active · export · bootstrap。"""

from __future__ import annotations

import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import yaml

SCHEMA_VERSION = "1"

BUNDLE_LAYOUT: dict[str, str] = {
    "persona/persona_registry.yaml": "users/{user}/persona/persona_registry.yaml",
    "cognitive/planes_registry.yaml": "cognitive/planes_registry.yaml",
    "cognitive/pathways_registry.yaml": "cognitive/pathways_registry.yaml",
    "cognitive/persona_modules.yaml": "cognitive/persona_modules.yaml",
    "brain/modules.client.yaml": "brain/modules.client.yaml",
}


def _now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def _init_id_now() -> str:
    now = datetime.now(timezone.utc)
    stamp = now.strftime("%Y%m%dT%H%M%S")
    ms = f"{now.microsecond // 1000:03d}"
    return f"init-{stamp}{ms}Z"


class TenantInitStore:
    def __init__(self, home: Path) -> None:
        self._home = Path(home)
        self._root = self._home / "control" / "tenant_init"
        self._archive = self._root / "archive"
        self._active_path = self._root / "active.yaml"
        self._archive.mkdir(parents=True, exist_ok=True)

    @property
    def root(self) -> Path:
        return self._root

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

    def _save_active(self, data: dict[str, Any]) -> None:
        self._root.mkdir(parents=True, exist_ok=True)
        with self._active_path.open("w", encoding="utf-8") as f:
            yaml.safe_dump(data, f, allow_unicode=True, default_flow_style=False, sort_keys=False)

    def get_active(self) -> dict[str, Any] | None:
        data = self._load_active()
        init_id = data.get("active_init_id")
        if not init_id:
            return None
        return {
            "active_init_id": str(init_id),
            "label": data.get("label"),
            "activated_at": data.get("activated_at"),
            "activated_by": data.get("activated_by"),
            "previous_init_id": data.get("previous_init_id"),
        }

    def list_archives(self) -> list[dict[str, Any]]:
        rows: list[dict[str, Any]] = []
        if not self._archive.is_dir():
            return rows
        for path in sorted(self._archive.iterdir()):
            if not path.is_dir():
                continue
            manifest_path = path / "manifest.yaml"
            if not manifest_path.is_file():
                continue
            with manifest_path.open(encoding="utf-8") as f:
                manifest = yaml.safe_load(f) or {}
            if isinstance(manifest, dict):
                rows.append(manifest)
        return rows

    def archive_dir(self, init_id: str) -> Path:
        return self._archive / init_id

    def bundle_dir(self, init_id: str) -> Path:
        return self.archive_dir(init_id) / "bundle"

    def _export_sources(self, config: dict[str, Any]) -> dict[str, Path]:
        from niannian.users.paths import persona_registry_path

        sources: dict[str, Path] = {}
        pinn_persona = persona_registry_path(self._home, "pinn")
        if pinn_persona.is_file():
            sources["persona/persona_registry.yaml"] = pinn_persona

        repo_root = Path(__file__).resolve().parents[2]
        cognitive = config.get("cognitive") or {}
        fallbacks = {
            "cognitive/planes_registry.yaml": cognitive.get("plane_registry")
            or "config/cognitive/plane_registry.yaml",
            "cognitive/pathways_registry.yaml": cognitive.get("pathway_registry")
            or "config/cognitive/pathway_registry.yaml",
            "cognitive/persona_modules.yaml": cognitive.get("persona_modules")
            or "config/cognitive/persona_registry.yaml",
            "brain/modules.client.yaml": "config/llm/modules.yaml",
        }
        for bundle_rel, raw in fallbacks.items():
            if bundle_rel in sources:
                continue
            for candidate in (Path(str(raw)), self._home / str(raw), repo_root / str(raw)):
                if candidate.is_file():
                    sources[bundle_rel] = candidate
                    break
        return sources

    def export_init(
        self,
        *,
        config: dict[str, Any],
        label: str,
        activated_by: str = "pinn",
        set_active: bool = True,
    ) -> dict[str, Any]:
        init_id = _init_id_now()
        archive = self.archive_dir(init_id)
        bundle = archive / "bundle"
        bundle.mkdir(parents=True, exist_ok=True)

        sources = self._export_sources(config)
        copied: list[str] = []
        for bundle_rel, src in sources.items():
            dest = bundle / bundle_rel
            dest.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src, dest)
            copied.append(bundle_rel)

        repo_root = Path(__file__).resolve().parents[2]
        skills_src = repo_root / "skills"
        if skills_src.is_dir():
            catalog_dest = bundle / "skills" / "catalog"
            if catalog_dest.exists():
                shutil.rmtree(catalog_dest)
            shutil.copytree(
                skills_src,
                catalog_dest,
                ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
                dirs_exist_ok=True,
            )
            copied.append("skills/catalog/")

        soul_src = repo_root / "config" / "soul"
        if soul_src.is_dir():
            defaults_dest = bundle / "soul" / "defaults"
            defaults_dest.mkdir(parents=True, exist_ok=True)
            for item in soul_src.glob("*.yaml"):
                shutil.copy2(item, defaults_dest / item.name)
                copied.append(f"soul/defaults/{item.name}")

        if not copied:
            placeholder = bundle / "persona" / "persona_registry.yaml"
            placeholder.parent.mkdir(parents=True, exist_ok=True)
            placeholder.write_text(
                yaml.safe_dump(
                    {"presets": {}, "schema_version": SCHEMA_VERSION},
                    allow_unicode=True,
                ),
                encoding="utf-8",
            )
            copied.append("persona/persona_registry.yaml")

        manifest = {
            "id": init_id,
            "label": label,
            "created_at": _now_iso(),
            "schema_version": SCHEMA_VERSION,
            "files": copied,
        }
        with (archive / "manifest.yaml").open("w", encoding="utf-8") as f:
            yaml.safe_dump(manifest, f, allow_unicode=True, default_flow_style=False, sort_keys=False)

        if set_active:
            self.set_active(init_id, label=label, activated_by=activated_by)

        return {"init_id": init_id, "label": label, "files": copied, "set_active": set_active}

    def set_active(
        self,
        init_id: str,
        *,
        label: str | None = None,
        activated_by: str = "pinn",
    ) -> dict[str, Any]:
        archive = self.archive_dir(init_id)
        if not archive.is_dir():
            raise KeyError(init_id)
        manifest_path = archive / "manifest.yaml"
        manifest: dict[str, Any] = {}
        if manifest_path.is_file():
            with manifest_path.open(encoding="utf-8") as f:
                loaded = yaml.safe_load(f) or {}
            if isinstance(loaded, dict):
                manifest = loaded
        prev = self._load_active().get("active_init_id")
        payload = {
            "active_init_id": init_id,
            "label": label or manifest.get("label") or init_id,
            "activated_at": _now_iso(),
            "activated_by": activated_by,
            "previous_init_id": prev,
        }
        self._save_active(payload)
        return payload

    def resolve_init_id(self, init_id: str | None) -> str:
        if init_id:
            if not self.archive_dir(init_id).is_dir():
                raise KeyError(init_id)
            return init_id
        active = self.get_active()
        if active is None:
            raise ValueError("no_active_init")
        return str(active["active_init_id"])

    def bootstrap_user(
        self,
        *,
        username: str,
        target_home: Path,
        init_id: str | None = None,
    ) -> dict[str, Any]:
        resolved = self.resolve_init_id(init_id)
        bundle = self.bundle_dir(resolved)
        if not bundle.is_dir():
            raise KeyError(resolved)

        written: list[str] = []
        user = username.strip().lower()

        for bundle_rel, dest_tpl in BUNDLE_LAYOUT.items():
            src = bundle / bundle_rel
            if src.is_file():
                dest = Path(target_home) / dest_tpl.format(user=user)
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src, dest)
                written.append(dest_tpl.format(user=user))

        skills_catalog = bundle / "skills" / "catalog"
        if skills_catalog.is_dir():
            for src in skills_catalog.rglob("*"):
                if not src.is_file():
                    continue
                rel = src.relative_to(skills_catalog).as_posix()
                dest_rel = f"skills/catalog/{rel}"
                dest = Path(target_home) / dest_rel
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src, dest)
                written.append(dest_rel)

        soul_defaults = bundle / "soul" / "defaults"
        if soul_defaults.is_dir():
            for src in soul_defaults.glob("*.yaml"):
                dest_rel = f"soul/defaults/{src.name}"
                dest = Path(target_home) / dest_rel
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src, dest)
                written.append(dest_rel)

        marker = Path(target_home) / "users" / user / ".tenant_bootstrap"
        marker.parent.mkdir(parents=True, exist_ok=True)
        marker.write_text(
            yaml.safe_dump(
                {"init_id": resolved, "at": _now_iso(), "schema_version": SCHEMA_VERSION},
                allow_unicode=True,
            ),
            encoding="utf-8",
        )
        written.append(f"users/{user}/.tenant_bootstrap")

        return {
            "init_id": resolved,
            "schema_version": SCHEMA_VERSION,
            "files_written": written,
        }
