"""SpecResolver 单测（Prompt 外置方案A · 步4）。

`SpecResolver` 统一内置（`SPECS`，纯内存、零 DB）与用户 skill（`SkillRegistry`，DB）
的只读解析入口。核心约束（不变量 #3 / 评审 HIGH）：

- `get(name)` 先查内置 `SPECS`（纯内存），命中内置 name **绝不触发任何 DB/registry 调用**；
- 未命中内置才查 `SkillRegistry`；都无 → `AppError(NOT_FOUND)`；
- `output_schema_for`：内置 → `SCHEMA_CATALOG[name]` 真类；纯用户 skill → None；
  name 精确字符串相等（无大小写/连字符归一），拼错近似 name → None 不报错；
- `resolver.build` 纯合并，**不**做保留名冲突校验（守卫前移至 SkillRegistry 入库）。
"""

from __future__ import annotations

import pytest
from ww_agents import (
    SCHEMA_CATALOG,
    SPECS,
    AgentSpec,
)
from ww_shared import AppError, ErrorCode
from ww_skills import SpecResolver


class _FakeRegistry:
    """断言用 fake：记录 get/names 是否被调用（内置 get 必须零触达）。"""

    def __init__(self, specs: dict[str, AgentSpec] | None = None) -> None:
        self._specs = dict(specs or {})
        self.get_calls: list[str] = []
        self.names_calls: int = 0

    def get(self, name: str) -> AgentSpec:
        self.get_calls.append(name)
        spec = self._specs.get(name)
        if spec is None:
            raise AppError(ErrorCode.NOT_FOUND, f"skill not found: {name}")
        return spec

    def names(self) -> list[str]:
        self.names_calls += 1
        return sorted(self._specs)

    def list_scope(self, scope: str) -> list[AgentSpec]:
        return [s for s in sorted(self._specs.values(), key=lambda s: s.name) if s.scope == scope]


def _user_spec(name: str, scope: str = "custom") -> AgentSpec:
    return AgentSpec(
        name=name,
        tier="writer",
        system_prompt=f"prompt for {name}",
        input_schema=None,
        output_schema=None,
        reads=(),
        writes=(),
        scope=scope,
    )


# ---- #7 resolver 内置/用户对齐 + 零 DB ----


def test_get_builtin_returns_specs_instance_without_touching_registry() -> None:
    # Arrange：fake registry 本身有同名条目也无所谓——内置命中必须零触达。
    fake = _FakeRegistry({"continuity": _user_spec("continuity")})
    resolver = SpecResolver.build(fake)

    # Act
    spec = resolver.get("continuity")

    # Assert：拿到的是内置同一实例，且 registry.get 从未被调用（零 DB）。
    assert spec is SPECS["continuity"]
    assert fake.get_calls == []


def test_get_user_skill_falls_through_to_registry() -> None:
    fake = _FakeRegistry({"my-skill": _user_spec("my-skill")})
    resolver = SpecResolver.build(fake)

    spec = resolver.get("my-skill")

    assert spec.name == "my-skill"
    assert fake.get_calls == ["my-skill"]


def test_get_unknown_everywhere_raises_not_found() -> None:
    fake = _FakeRegistry()
    resolver = SpecResolver.build(fake)

    with pytest.raises(AppError) as exc:
        resolver.get("nope-not-here")

    assert exc.value.code is ErrorCode.NOT_FOUND
    assert fake.get_calls == ["nope-not-here"]


def test_names_merges_builtin_and_user() -> None:
    fake = _FakeRegistry({"my-skill": _user_spec("my-skill")})
    resolver = SpecResolver.build(fake)

    names = resolver.names()

    assert "continuity" in names
    assert "my-skill" in names


def test_list_scope_filters_builtin_and_user() -> None:
    fake = _FakeRegistry({"my-skill": _user_spec("my-skill", scope="custom")})
    resolver = SpecResolver.build(fake)

    builtin = resolver.list_scope("builtin")
    custom = resolver.list_scope("custom")

    assert all(s.scope == "builtin" for s in builtin)
    assert {s.name for s in custom} == {"my-skill"}


# ---- #9 output_schema_for ----


def test_output_schema_for_builtin_returns_real_type() -> None:
    fake = _FakeRegistry()
    resolver = SpecResolver.build(fake)

    assert resolver.output_schema_for("continuity") is SCHEMA_CATALOG["continuity"]


def test_output_schema_for_refiner_is_none() -> None:
    fake = _FakeRegistry()
    resolver = SpecResolver.build(fake)

    assert resolver.output_schema_for("refiner") is None


def test_output_schema_for_pure_user_skill_is_none() -> None:
    fake = _FakeRegistry({"my-skill": _user_spec("my-skill")})
    resolver = SpecResolver.build(fake)

    assert resolver.output_schema_for("my-skill") is None


def test_output_schema_for_misspelled_name_is_none_no_fuzzy_match() -> None:
    # 拼错近似 name（下划线）不得误命中连字符内置 "character-gen"（精确字符串相等）。
    fake = _FakeRegistry()
    resolver = SpecResolver.build(fake)

    assert resolver.output_schema_for("character_gen") is None
    assert resolver.output_schema_for("character-gen") is not None
