"""质量校验模块单元测试。"""

from __future__ import annotations

from app.tree.index import (
    IndexMeta,
    L1Card,
    L1Node,
    L2Card,
    L2Node,
    L3Card,
    L3Node,
    TreeIndex,
)
from app.tree.verify import VerifyStats, _normalize, fuzzy_match, verify_tree


class TestNormalize:
    def test_lowercase(self):
        assert _normalize("Hello World") == "hello world"

    def test_strip_punctuation(self):
        assert _normalize("Hello, World!") == "hello world"

    def test_empty(self):
        assert _normalize("") == ""


class TestFuzzyMatch:
    def test_exact_match(self):
        assert fuzzy_match("hello", "hello world")

    def test_case_insensitive(self):
        assert fuzzy_match("Hello", "say hello world")

    def test_no_match(self):
        assert not fuzzy_match("xyz", "hello world")

    def test_none_entity(self):
        assert not fuzzy_match(None, "hello")

    def test_none_corpus(self):
        assert not fuzzy_match("hello", None)


class TestVerifyTree:
    def _make_tree(self):
        """构造一棵树，L2 有混合实体（有出处/无出处）。"""
        l3_0 = L3Node(
            id="l1_0_l2_0_l3_0",
            card=L3Card(
                frame_summary="一个运动员在跑步",
                visible_entities=["运动员", "跑道"],
                ongoing_actions=["跑步"],
                visible_text=["Nike", "2024"],
                spatial_layout="居中",
                visual_attributes={},
                subtitle="the athlete is running fast",
            ),
            timestamp=1.0,
        )
        l3_1 = L3Node(
            id="l1_0_l2_0_l3_1",
            card=L3Card(
                frame_summary="观众在欢呼",
                visible_entities=["观众"],
                ongoing_actions=["欢呼"],
                visible_text=["Stadium"],
                spatial_layout="广角",
                visual_attributes={},
            ),
            timestamp=3.0,
        )
        l2 = L2Node(
            id="l1_0_l2_0",
            card=L2Card(
                event_description="比赛片段",
                entities=["运动员", "裁判", "幻觉实体"],  # "裁判"和"幻觉实体"无 L3 出处
                actions=["跑步"],
                action_subjects=["运动员"],
                visible_text=["Nike", "不存在的文字"],  # "不存在的文字"无 L3 出处
                spatial_relations="",
                state_changes=None,
            ),
            time_range=(0.0, 10.0),
            children=[l3_0, l3_1],
        )
        l1 = L1Node(
            id="l1_0",
            card=L1Card(
                scene_summary="体育比赛",
                main_setting="体育场",
                key_entities=["运动员", "不存在的人"],  # "不存在的人"无出处
                main_actions=["比赛"],
                topic_keywords=["体育"],
                visible_text=["Nike", "Ghost"],  # "Ghost"无出处
                temporal_flow="从左到右",
            ),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        return TreeIndex(metadata=IndexMeta("/test.mp4", "video"), roots=[l1])

    def test_removes_ungrounded_l2_entities(self):
        index = self._make_tree()
        stats = verify_tree(index)
        l2 = index.roots[0].children[0]
        assert "运动员" in l2.card.entities
        assert "幻觉实体" not in l2.card.entities
        assert stats.l2_entities_removed >= 1

    def test_removes_ungrounded_l2_visible_text(self):
        index = self._make_tree()
        stats = verify_tree(index)
        l2 = index.roots[0].children[0]
        assert "Nike" in l2.card.visible_text
        assert "不存在的文字" not in l2.card.visible_text
        assert stats.l2_visible_text_removed >= 1

    def test_removes_ungrounded_l1_visible_text(self):
        index = self._make_tree()
        stats = verify_tree(index)
        l1 = index.roots[0]
        assert "Nike" in l1.card.visible_text
        assert "Ghost" not in l1.card.visible_text
        assert stats.l1_visible_text_removed >= 1

    def test_removes_ungrounded_l1_key_entities(self):
        index = self._make_tree()
        stats = verify_tree(index)
        l1 = index.roots[0]
        assert "运动员" in l1.card.key_entities
        assert "不存在的人" not in l1.card.key_entities
        assert stats.l1_key_entities_removed >= 1

    def test_preserves_grounded_entities(self):
        index = self._make_tree()
        verify_tree(index)
        l2 = index.roots[0].children[0]
        assert "运动员" in l2.card.entities

    def test_returns_verify_stats(self):
        index = self._make_tree()
        stats = verify_tree(index)
        assert isinstance(stats, VerifyStats)
        total_kept = stats.l2_entities_kept + stats.l1_key_entities_kept
        assert total_kept > 0

    def test_frozen_card_replaced(self):
        """验证 Card 被替换为新实例（frozen dataclass 不能原地修改）。"""
        index = self._make_tree()
        old_l2_card = index.roots[0].children[0].card
        verify_tree(index)
        new_l2_card = index.roots[0].children[0].card
        # Card should be a different object if anything was removed
        assert old_l2_card is not new_l2_card
