"""T3.3 三审契约测试：foreshadow / pace spec + 输出 schema（C6 扩）。

契约测试——构造符合 schema 的 mock 响应，校验字段、tier、四审只读（writes==[]）。
不联网、无 DB。
"""

from __future__ import annotations

import pytest
from pydantic import ValidationError
from ww_agents import (
    AgentSpec,
    ForeshadowReview,
    ForeshadowSuggestion,
    PaceIssue,
    PaceReview,
    foreshadow_spec,
    pace_spec,
)

# ---- ForeshadowReview schema ----


def test_foreshadow_review_parses_mock_response() -> None:
    # Arrange：模拟网关 instructor 校验后的结构化产出
    mock = {
        "planted": [
            {
                "code": "FS-MARK",
                "title": "胸口的胎记",
                "where": "第 2 段主角沐浴时被瞥见",
                "note": "暗示皇族血统",
            }
        ],
        "resolved": [
            {
                "code": "FS-BIRTH",
                "title": "身世之谜",
                "where": "第 9 段老者道破来历",
                "note": "回收第 1 章埋设",
            }
        ],
    }

    # Act
    review = ForeshadowReview.model_validate(mock)

    # Assert
    assert len(review.planted) == 1
    assert review.planted[0].code == "FS-MARK"
    assert review.resolved[0].code == "FS-BIRTH"
    assert review.resolved[0].where == "第 9 段老者道破来历"


def test_foreshadow_review_defaults_to_empty_lists() -> None:
    review = ForeshadowReview()
    assert review.planted == []
    assert review.resolved == []


def test_foreshadow_suggestion_only_title_required() -> None:
    sug = ForeshadowSuggestion.model_validate({"title": "新埋的悬念"})
    assert sug.title == "新埋的悬念"
    assert sug.code is None
    assert sug.where is None
    assert sug.note is None


def test_foreshadow_suggestion_requires_title() -> None:
    with pytest.raises(ValidationError):
        ForeshadowSuggestion.model_validate({"code": "FS-X"})


# ---- PaceReview schema ----


def test_pace_review_parses_mock_response() -> None:
    mock = {
        "water": [{"where": "第 4–6 段反复描写天气", "reason": "信息密度低、与主线无关"}],
        "hook": True,
        "beat_map": [1, 3, 2, 0, 4, 5],
    }

    review = PaceReview.model_validate(mock)

    assert len(review.water) == 1
    assert review.water[0].reason == "信息密度低、与主线无关"
    assert review.hook is True
    assert review.beat_map == [1, 3, 2, 0, 4, 5]


def test_pace_review_defaults() -> None:
    review = PaceReview()
    assert review.water == []
    assert review.hook is False
    assert review.beat_map == []


def test_pace_issue_requires_where_and_reason() -> None:
    with pytest.raises(ValidationError):
        PaceIssue.model_validate({"where": "x"})


# ---- foreshadow_spec 声明 ----


def test_foreshadow_spec_is_analyst_tier() -> None:
    assert foreshadow_spec.tier == "analyst"
    assert foreshadow_spec.name == "foreshadow"


def test_foreshadow_spec_is_read_only() -> None:
    # 不变量 #3：四审只读
    assert foreshadow_spec.writes == ()


def test_foreshadow_spec_reads_foreshadow() -> None:
    assert foreshadow_spec.reads == ("foreshadow",)


def test_foreshadow_spec_output_schema() -> None:
    assert foreshadow_spec.output_schema is ForeshadowReview


def test_foreshadow_spec_has_nonempty_system_prompt() -> None:
    assert foreshadow_spec.system_prompt.strip()


# ---- pace_spec 声明 ----


def test_pace_spec_is_light_tier() -> None:
    # 不变量 #2：节奏审用轻量档，只声明 tier
    assert pace_spec.tier == "light"
    assert pace_spec.name == "pace"


def test_pace_spec_is_read_only() -> None:
    assert pace_spec.writes == ()


def test_pace_spec_reads_rules() -> None:
    # genre 模板 DSL 经 rules（genre 级）注入
    assert pace_spec.reads == ("rules",)


def test_pace_spec_output_schema() -> None:
    assert pace_spec.output_schema is PaceReview


def test_pace_spec_has_nonempty_system_prompt() -> None:
    assert pace_spec.system_prompt.strip()


# ---- 共性：immutable + 是 AgentSpec ----


def test_review_specs_are_agent_specs() -> None:
    assert isinstance(foreshadow_spec, AgentSpec)
    assert isinstance(pace_spec, AgentSpec)


def test_review_specs_are_immutable() -> None:
    with pytest.raises(ValidationError):
        foreshadow_spec.tier = "writer"
    with pytest.raises(ValidationError):
        pace_spec.tier = "writer"
