"""灵感③ 人物塑造续审契约测试：CharacterizationReview schema + characterization_spec。

advisory 单维——仅建议、不阻断验收（D2）。

契约测试——构造符合 schema 的 mock 响应，校验字段默认值降级、tier、只读（writes==()）、
输出 schema 派生。人物塑造是 advisory：**不加入 REVIEW_RESERVED_NAMES**。不联网、无 DB。
"""

from __future__ import annotations

import pytest
from pydantic import ValidationError
from ww_agents import (
    REVIEW_RESERVED_NAMES,
    AgentSpec,
    CharacterizationIssue,
    CharacterizationReview,
    characterization_spec,
)

# ---- CharacterizationReview schema ----


def test_characterization_parses_mock_response() -> None:
    # Arrange：模拟网关 instructor 校验后的结构化产出（每条带逐字引用 + 置信度）
    mock = {
        "issues": [
            {
                "character": "沈砚",
                "aspect": "动机一致性",
                "where": "第4段决斗前退缩",
                "quote": "他忽然转身就走，什么也没说。",
                "diagnosis": "与人物卡既定「护妹至死」动机断裂，无过渡",
                "suggestion": "让退缩扣回护妹动机（如察觉陷阱护妹先撤）",
                "confidence": 0.82,
            }
        ]
    }

    # Act
    review = CharacterizationReview.model_validate(mock)

    # Assert
    assert len(review.issues) == 1
    issue = review.issues[0]
    assert issue.character == "沈砚"
    assert issue.aspect == "动机一致性"
    assert issue.quote == "他忽然转身就走，什么也没说。"
    assert issue.confidence == pytest.approx(0.82)


def test_characterization_defaults_to_empty_issues() -> None:
    # 无人物卡 / 无问题 → 空 issues（默认值降级，仿 StyleDriftReview 韧性）
    review = CharacterizationReview()
    assert review.issues == []


def test_characterization_issue_all_fields_have_defaults() -> None:
    # 畸形/缺字段守韧性：全字段给默认值，空 dict 也能解析
    issue = CharacterizationIssue.model_validate({})
    assert issue.character == ""
    assert issue.aspect == ""
    assert issue.where == ""
    assert issue.quote == ""
    assert issue.diagnosis == ""
    assert issue.suggestion == ""
    assert issue.confidence == 0.0


def test_characterization_issue_partial_fields_ok() -> None:
    # 缺 confidence/quote 时仍解析（降级默认），不抛
    issue = CharacterizationIssue.model_validate({"character": "阿黎", "diagnosis": "扁平"})
    assert issue.character == "阿黎"
    assert issue.diagnosis == "扁平"
    assert issue.confidence == 0.0


# ---- characterization_spec 声明（analyst 档，advisory 单维）----


def test_characterization_spec_is_analyst_tier() -> None:
    # 不变量 #2：只声明档位，不写 model
    assert characterization_spec.tier == "analyst"
    assert characterization_spec.name == "characterization"


def test_characterization_spec_is_read_only() -> None:
    # 不变量 #3：审查只读，唯一落库=collect 留痕
    assert characterization_spec.writes == ()


def test_characterization_spec_reads_characters() -> None:
    # 以人物卡 motive/appearance 为客观锚点
    assert characterization_spec.reads == ("characters",)


def test_characterization_spec_output_schema() -> None:
    assert characterization_spec.output_schema is CharacterizationReview


def test_characterization_spec_prompt_ignores_length_and_diction() -> None:
    # 对抗 LLM-judge 冗长/辞藻偏见：prompt 必须显式声明忽略文本长度与辞藻华丽度
    prompt = characterization_spec.system_prompt
    assert "忽略文本长度与辞藻华丽度" in prompt


def test_characterization_not_in_review_reserved_names() -> None:
    # advisory：不进受信保留名（不阻断验收、无 conflicts 通道）
    assert "characterization" not in REVIEW_RESERVED_NAMES


def test_characterization_spec_is_agent_spec_and_immutable() -> None:
    assert isinstance(characterization_spec, AgentSpec)
    with pytest.raises(ValidationError):
        characterization_spec.tier = "writer"
