"""core/evolution/types.py 的类型构造与约束测试。

验证:
  - frozen 类型不可变性
  - mutable 类型可修改
  - 全部 18 个类型可正确构造
  - 默认值正确性
  - 字段完整性
"""

from __future__ import annotations

import pytest

from core.evolution.types import (
    CaseSample,
    DiagnosePrompts,
    DiagnosisResult,
    ErrorAttribution,
    EvolutionRecord,
    EvolutionResult,
    EvolvePrompts,
    GateParams,
    GateVerdict,
    PairResult,
    QuadrantClassification,
    QuestionMetrics,
    RejectedEdit,
    SkillCasePack,
    SkillStepAdherence,
    SpanMetrics,
    SystemCasePack,
    ToolCasePack,
)

# ---------------------------------------------------------------------------
# Gate 类型
# ---------------------------------------------------------------------------


def test_gate_params_frozen():
    """GateParams 是 frozen dataclass，构造后不可修改。"""
    p = GateParams(
        e_confirm=20.0,
        e_provisional=3.0,
        w_net_min=2,
        delta_min=0.02,
        lambda_dir=-0.642,
        e_rollback=10.0,
    )
    assert p.e_confirm == 20.0
    with pytest.raises(AttributeError):
        p.e_confirm = 1.0


def test_gate_verdict_frozen():
    """GateVerdict 是 frozen dataclass。"""
    v = GateVerdict(
        decision="accept_confirmed",
        e_value=25.0,
        wald_lambda=1.2,
        delta_hat=0.15,
        delta_shrunk=0.12,
    )
    assert v.decision == "accept_confirmed"
    with pytest.raises(AttributeError):
        v.decision = "reject"


# ---------------------------------------------------------------------------
# 诊断类型
# ---------------------------------------------------------------------------


def test_span_metrics_frozen():
    """SpanMetrics 是 frozen dataclass，含默认空列表。"""
    sm = SpanMetrics(
        step=1,
        tool_name="view_node",
        extraction_completeness=0.9,
        hallucination_rate=0.05,
    )
    assert sm.step == 1
    assert sm.missed_info_tags == []
    assert sm.hallucination_tags == []
    with pytest.raises(AttributeError):
        sm.step = 2


def test_skill_step_adherence_frozen():
    """SkillStepAdherence 是 frozen dataclass。"""
    sa = SkillStepAdherence(
        step_label="定位目标层级",
        adhered=True,
        description="正确遵循了定位步骤",
    )
    assert sa.adhered is True
    with pytest.raises(AttributeError):
        sa.adhered = False


def test_question_metrics_frozen():
    """QuestionMetrics 是 frozen dataclass，约 17 个字段。"""
    qm = QuestionMetrics(
        question_id="q001",
        video_id="v001",
        task_type="Action Reasoning",
        correct=False,
        format_compliance=1.0,
        budget_usage=0.6,
        confidence_calibration="calibrated",
        repeat_visit_rate=0.1,
        search_keyword_repetition=0.0,
        level_jump_pattern="L1→L2→L3",
        tool_usage={"view_node": 3, "search_similar": 1},
        span_metrics=[],
        missed_nodes=["L2_seg_01"],
        skill_adherence=[],
        confirmation_bias=None,
        evidence_sufficient=True,
    )
    assert qm.question_id == "q001"
    assert qm.degraded is False  # 默认值
    with pytest.raises(AttributeError):
        qm.correct = True


def test_error_attribution_frozen():
    """ErrorAttribution 是 frozen dataclass，含可选字段。"""
    ea = ErrorAttribution(
        question_id="q001",
        error_type="search_failure",
        reasoning_failure_type=None,
        cause_category="defect",
        lapse_note=None,
    )
    assert ea.cause_category == "defect"
    with pytest.raises(AttributeError):
        ea.cause_category = "lapse"


def test_case_sample_frozen():
    """CaseSample 是 frozen dataclass，含完整推理轨迹。"""
    cs = CaseSample(
        question_id="q001",
        video_id="v001",
        task_type="Temporal Reasoning",
        question="视频中发生了什么？",
        options=["A. 跑步", "B. 走路"],
        answer="A",
        prediction="B",
        correct=False,
        error_type="reasoning_failure",
        selection_reason="error_type=reasoning_failure, severity=(1, 0.6)",
        metrics={"correct": False, "budget_usage": 0.6},
        trace=[{"step": 1, "tool_name": "view_node", "tool_output": "..."}],
    )
    assert cs.prediction == "B"
    with pytest.raises(AttributeError):
        cs.prediction = "A"


def test_skill_case_pack_frozen():
    """SkillCasePack 是 frozen dataclass，含默认空列表。"""
    pack = SkillCasePack(
        task_type="Action Reasoning",
        target_file="action-reasoning.md",
        stats={"n_total": 10, "accuracy": 0.7},
    )
    assert pack.failure_cases == []
    assert pack.success_cases == []
    assert pack.lapse_notes == []
    with pytest.raises(AttributeError):
        pack.task_type = "other"


def test_system_case_pack_frozen():
    """SystemCasePack 是 frozen dataclass。"""
    pack = SystemCasePack(stats={"early_submit_count": 5})
    assert pack.failure_cases == []
    assert pack.success_cases == []
    with pytest.raises(AttributeError):
        pack.stats = {}


def test_tool_case_pack_frozen():
    """ToolCasePack 是 frozen dataclass。"""
    pack = ToolCasePack(
        tool_name="view_node",
        target_files=["view_node_extract.md", "view_node_verify.md"],
        stats={"avg_completeness": 0.85},
    )
    assert pack.failure_spans == []
    assert pack.success_spans == []
    with pytest.raises(AttributeError):
        pack.tool_name = "other"


def test_diagnosis_result_frozen():
    """DiagnosisResult 是 frozen dataclass，约 18 个字段。"""
    dr = DiagnosisResult(run_id="run_001")
    assert dr.run_id == "run_001"
    assert dr.filter_summary == {}
    assert dr.error_attributions == []
    assert dr.system_case_pack is None
    assert dr.infra_excluded_count == 0
    assert dr.infra_excluded_ratio == 0.0
    assert dr.defect_count == 0
    assert dr.lapse_count == 0
    assert dr.degraded_count == 0
    assert dr.degraded_question_ids == []
    with pytest.raises(AttributeError):
        dr.run_id = "other"


# ---------------------------------------------------------------------------
# 进化类型
# ---------------------------------------------------------------------------


def test_evolution_record_mutable():
    """EvolutionRecord 是 mutable dataclass，构建过程中需修改。"""
    r = EvolutionRecord(
        target_file="test.md",
        target_type="skill",
        original_content="a",
        evolved_content="b",
        reason="test",
        status="accepted",
        source_version="v1",
        suggestions=[],
        edits=[],
        apply_report=[],
        clip_info={},
    )
    r.status = "rejected"
    assert r.status == "rejected"


def test_evolution_record_defaults():
    """EvolutionRecord 各默认字段值正确。"""
    r = EvolutionRecord(
        target_file="x.md",
        target_type="skill",
        original_content="orig",
        evolved_content="new",
        reason="pass",
        status="accepted",
        source_version="v1",
    )
    assert r.result_version is None
    assert r.suggestions == []
    assert r.attempts == []
    assert r.validation_errors == []
    assert r.edits == []
    assert r.apply_report == []
    assert r.clip_info == {"triggered": False, "clipped": 0}


def test_rejected_edit_frozen():
    """RejectedEdit 是 frozen dataclass，含 gate 证据可选字段。"""
    re_ = RejectedEdit(
        target_file="temporal-reasoning.md",
        target_type="skill",
        change_summary="增加了时序推理步骤",
        delta=-0.05,
        source_version="v2",
        epoch=3,
        gate_w=5,
        gate_l=8,
        gate_e_value=0.3,
        gate_delta_shrunk=-0.02,
    )
    assert re_.gate_w == 5
    with pytest.raises(AttributeError):
        re_.delta = 0.0


def test_rejected_edit_optional_gate_fields():
    """RejectedEdit gate 字段默认为 None。"""
    re_ = RejectedEdit(
        target_file="x.md",
        target_type="skill",
        change_summary="test",
        delta=0.0,
        source_version="v1",
        epoch=1,
    )
    assert re_.gate_w is None
    assert re_.gate_l is None
    assert re_.gate_e_value is None
    assert re_.gate_delta_shrunk is None


def test_evolution_result_frozen():
    """EvolutionResult 是 frozen dataclass，不含 skills_version/prompts_version。"""
    result = EvolutionResult(
        records=[],
        accepted_count=2,
        rejected_count=1,
        skipped_count=0,
    )
    assert result.accepted_count == 2
    with pytest.raises(AttributeError):
        result.accepted_count = 0
    # 确认不含 TRM4 的 skills_version/prompts_version
    assert not hasattr(result, "skills_version")
    assert not hasattr(result, "prompts_version")


# ---------------------------------------------------------------------------
# 验证辅助类型
# ---------------------------------------------------------------------------


def test_pair_result_frozen():
    """PairResult 是 frozen dataclass。"""
    pr = PairResult(
        w=3,
        l=1,
        observed={"q1": (False, True), "q2": (True, False)},
    )
    assert pr.w == 3
    with pytest.raises(AttributeError):
        pr.w = 0


def test_quadrant_classification_frozen():
    """QuadrantClassification 是 frozen dataclass，四象限分类。"""
    qc = QuadrantClassification(
        improvements=["q1", "q3"],
        regressions=["q2"],
        persistent_fails=["q4"],
        stable_successes=["q5", "q6"],
    )
    assert len(qc.improvements) == 2
    with pytest.raises(AttributeError):
        qc.improvements = []


# ---------------------------------------------------------------------------
# Prompt 模板束
# ---------------------------------------------------------------------------


def test_diagnose_prompts_frozen():
    """DiagnosePrompts 是 frozen dataclass，8 个模板字段。"""
    dp = DiagnosePrompts(
        defect_vs_lapse="p1",
        reasoning_sub="p2",
        span_eval_system="p3",
        span_eval_user="p4",
        missed_nodes="p5",
        skill_adherence="p6",
        confirmation_bias="p7",
        evidence_sufficiency="p8",
    )
    assert dp.defect_vs_lapse == "p1"
    with pytest.raises(AttributeError):
        dp.defect_vs_lapse = "other"


def test_evolve_prompts_frozen():
    """EvolvePrompts 是 frozen dataclass，5 个模板字段。"""
    ep = EvolvePrompts(
        evolve_skill="skill_tmpl",
        evolve_system="system_tmpl",
        evolve_tool="tool_tmpl",
        evolve_rank="rank_tmpl",
        consolidate_system="consolidate_tmpl",
    )
    assert ep.evolve_rank == "rank_tmpl"
    with pytest.raises(AttributeError):
        ep.evolve_skill = "other"
