"""GeneratedQuestion v2 类型扩展测试。

验证 skill_target 和 difficulty_steps 新字段的默认值、显式赋值、
frozen 不可变性，以及 loader 的前后向兼容性。
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    from pathlib import Path

from core.types import GeneratedQuestion


class TestGeneratedQuestionV2:
    """新增字段的单元测试。"""

    def test_new_fields_default_none(self) -> None:
        """无显式赋值时，skill_target 和 difficulty_steps 默认为 None。"""
        q = GeneratedQuestion(
            question_id="q1",
            video_id="v1",
            task_type="TN",
            question="What happens?",
            options=("A. yes", "B. no", "C. maybe", "D. none"),
            answer="A",
            source_nodes=("n1",),
            difficulty="medium",
        )
        assert q.skill_target is None
        assert q.difficulty_steps is None

    def test_new_fields_explicit(self) -> None:
        """显式赋值时字段正确存储。"""
        q = GeneratedQuestion(
            question_id="q2",
            video_id="v2",
            task_type="AR",
            question="Why did this occur?",
            options=("A. cause1", "B. cause2", "C. cause3", "D. cause4"),
            answer="B",
            source_nodes=("n2", "n3"),
            difficulty="hard",
            skill_target="M1",
            difficulty_steps=5,
        )
        assert q.skill_target == "M1"
        assert q.difficulty_steps == 5

    def test_frozen_immutable(self) -> None:
        """frozen=True 保证新字段不可修改。"""
        q = GeneratedQuestion(
            question_id="q3",
            video_id="v3",
            task_type="EN",
            question="How many items?",
            options=("A. 1", "B. 2", "C. 3", "D. 4"),
            answer="C",
            source_nodes=("n4",),
            difficulty="easy",
            skill_target="M2",
            difficulty_steps=3,
        )
        with pytest.raises(AttributeError):
            q.skill_target = "M3"  # type: ignore[misc]
        with pytest.raises(AttributeError):
            q.difficulty_steps = 7  # type: ignore[misc]


class TestLoaderV2Compat:
    """loader.py 前后向兼容性测试。"""

    def test_load_old_format_json(self, tmp_path: Path) -> None:
        """旧格式 JSON（无新字段）加载后新字段为 None。"""
        from app.question_gen.loader import load_benchmark

        data = [
            {
                "question_id": "old_q1",
                "task_type": "TN",
                "question": "What is shown?",
                "options": ["A. cat", "B. dog", "C. bird", "D. fish"],
                "answer": "A",
                "source_nodes": ["seg_01"],
                "difficulty": "easy",
            }
        ]
        json_file = tmp_path / "video_old.json"
        json_file.write_text(json.dumps(data), encoding="utf-8")

        questions = load_benchmark(tmp_path)
        assert len(questions) == 1
        q = questions[0]
        assert q.skill_target is None
        assert q.difficulty_steps is None
        assert q.video_id == "video_old"

    def test_load_new_format_json(self, tmp_path: Path) -> None:
        """新格式 JSON（含新字段）正确加载。"""
        from app.question_gen.loader import load_benchmark

        data = [
            {
                "question_id": "new_q1",
                "task_type": "AR",
                "question": "Why did X happen?",
                "options": ["A. r1", "B. r2", "C. r3", "D. r4"],
                "answer": "B",
                "source_nodes": ["seg_02", "seg_03"],
                "difficulty": "hard",
                "skill_target": "M3",
                "difficulty_steps": 7,
            }
        ]
        json_file = tmp_path / "video_new.json"
        json_file.write_text(json.dumps(data), encoding="utf-8")

        questions = load_benchmark(tmp_path)
        assert len(questions) == 1
        q = questions[0]
        assert q.skill_target == "M3"
        assert q.difficulty_steps == 7


class TestPoolsSerializationV2:
    """pools.py _q_to_dict / _dict_to_q 新字段兼容性测试。"""

    def test_roundtrip_with_new_fields(self) -> None:
        """新字段经过序列化反序列化后保持一致。"""
        from app.harness.pools import _dict_to_q, _q_to_dict

        q = GeneratedQuestion(
            question_id="rt_q1",
            video_id="v_rt",
            task_type="VS",
            question="Where is the object?",
            options=("A. left", "B. right", "C. top", "D. bottom"),
            answer="A",
            source_nodes=("node_1",),
            difficulty="medium",
            skill_target="M5",
            difficulty_steps=10,
        )
        d = _q_to_dict(q)
        assert d["skill_target"] == "M5"
        assert d["difficulty_steps"] == 10

        restored = _dict_to_q(d)
        assert restored.skill_target == "M5"
        assert restored.difficulty_steps == 10

    def test_roundtrip_without_new_fields(self) -> None:
        """旧字典（无新字段）反序列化时新字段默认为 None。"""
        from app.harness.pools import _dict_to_q

        d = {
            "question_id": "legacy_q1",
            "video_id": "v_legacy",
            "task_type": "TN",
            "question": "What?",
            "options": ["A", "B", "C", "D"],
            "answer": "A",
            "source_nodes": ["n1"],
            "difficulty": "medium",
        }
        q = _dict_to_q(d)
        assert q.skill_target is None
        assert q.difficulty_steps is None
