"""Prompt、Skill 与 Persona 注册中心。

注册中心统一回答三个问题：

- Prompt 文件存放在哪里；
- 一个 Skill 包含哪些业务方法和可用工具；
- 一个 Agent 人格可以加载哪些 Prompt、Skills 和工具。

Agent Kernel 只通过 ID 读取配置，不需要关心文件发现和解析细节。
"""

from dataclasses import dataclass
from pathlib import Path

from zbt.core.errors import AppError

ASSET_ROOT = Path(__file__).resolve().parent / "assets"


@dataclass(frozen=True)
class SkillDefinition:
    """从一个 ``SKILL.md`` 文件解析出的技能定义。"""

    id: str
    name: str
    personas: tuple[str, ...]
    tools: tuple[str, ...]
    instructions: str
    source: Path


@dataclass(frozen=True)
class PersonaDefinition:
    """一个 Agent 人格的配置；人格本质上是能力组合，而不是另一个模型。"""

    id: str
    name: str
    prompt_id: str
    skill_ids: tuple[str, ...]
    tool_names: tuple[str, ...]


class PromptRegistry:
    """发现并按文件名加载 Markdown 格式的系统提示词。"""

    def __init__(self, root: Path | None = None) -> None:
        self._root = root or ASSET_ROOT / "prompts"
        # 文件名就是 Prompt ID，例如 customer.md 对应 customer。
        self._items = {
            path.stem: path.read_text(encoding="utf-8").strip() for path in self._root.glob("*.md")
        }

    def get(self, prompt_id: str) -> str:
        """读取 Prompt；配置缺失时立即报错，避免 Agent 带着空提示词运行。"""

        prompt = self._items.get(prompt_id)
        if prompt is None:
            raise AppError(
                "HARNESS_PROMPT_NOT_FOUND",
                f"未找到Prompt：{prompt_id}",
                500,
            )
        return prompt

    def ids(self) -> tuple[str, ...]:
        return tuple(sorted(self._items))


class SkillRegistry:
    """发现标准 ``SKILL.md``，并解析本项目使用的最小前置元数据。"""

    def __init__(self, root: Path | None = None) -> None:
        self._root = root or ASSET_ROOT / "skills"
        self._items: dict[str, SkillDefinition] = {}
        for path in self._root.glob("*/SKILL.md"):
            # 每个技能一个目录，目录中使用统一名称 SKILL.md。
            skill = self._parse(path)
            if skill.id in self._items:
                raise AppError(
                    "HARNESS_SKILL_DUPLICATED",
                    f"Skill标识重复：{skill.id}",
                    500,
                )
            self._items[skill.id] = skill

    def get(self, skill_id: str) -> SkillDefinition:
        """按 ID 读取已经解析的 Skill。"""

        skill = self._items.get(skill_id)
        if skill is None:
            raise AppError(
                "HARNESS_SKILL_NOT_FOUND",
                f"未找到Skill：{skill_id}",
                500,
            )
        return skill

    def ids(self) -> tuple[str, ...]:
        return tuple(sorted(self._items))

    @staticmethod
    def _parse(path: Path) -> SkillDefinition:
        """解析 Skill 的元数据和正文指令。

        文件开头两个 ``---`` 之间是元数据，后面的正文会被加入系统提示词。
        """

        raw = path.read_text(encoding="utf-8").strip()
        if not raw.startswith("---\n"):
            raise AppError(
                "HARNESS_SKILL_INVALID",
                f"Skill缺少元数据：{path.parent.name}",
                500,
            )
        _, metadata_text, instructions = raw.split("---", maxsplit=2)
        metadata: dict[str, str] = {}
        for line in metadata_text.strip().splitlines():
            key, separator, value = line.partition(":")
            if separator:
                metadata[key.strip()] = value.strip()
        required = {"id", "name", "personas", "tools"}
        if not required.issubset(metadata):
            raise AppError(
                "HARNESS_SKILL_INVALID",
                f"Skill元数据不完整：{path.parent.name}",
                500,
            )
        return SkillDefinition(
            id=metadata["id"],
            name=metadata["name"],
            personas=tuple(_csv(metadata["personas"])),
            tools=tuple(_csv(metadata["tools"])),
            instructions=instructions.strip(),
            source=path,
        )


class PersonaRegistry:
    """保存第一阶段允许使用的两种 Agent 人格。"""

    def __init__(self) -> None:
        self._items = {
            # 客户人格可以查询和准备投保，但不能访问后台经营数据。
            "customer": PersonaDefinition(
                id="customer",
                name="客户保障顾问",
                prompt_id="customer",
                skill_ids=("insurance-consultation", "enrollment-guidance"),
                tool_names=(
                    "list_available_products",
                    "calculate_insurance_quote",
                    "prepare_enrollment",
                    "list_my_orders",
                    "list_my_policies",
                ),
            ),
            # 运营人格使用只读分析工具，不执行投保交易。
            "operation": PersonaDefinition(
                id="operation",
                name="运营数据助手",
                prompt_id="operation",
                skill_ids=("operation-insights",),
                tool_names=(
                    "get_operation_overview",
                    "list_recent_orders",
                    "list_recent_policies",
                    "get_attribution_performance",
                    "list_available_products",
                ),
            ),
        }

    def get(self, persona_id: str) -> PersonaDefinition:
        """查询人格配置；不允许临时使用未注册人格。"""

        persona = self._items.get(persona_id)
        if persona is None:
            raise AppError(
                "HARNESS_PERSONA_NOT_FOUND",
                f"未找到Agent人格：{persona_id}",
                500,
            )
        return persona


def _csv(value: str) -> list[str]:
    """把逗号分隔的元数据转换为去除空白的列表。"""

    return [item.strip() for item in value.split(",") if item.strip()]
