"""建树模块端到端集成测试。

验证各模块协作：
  构造最小树 → verify → subtitle 注入 → TreeEnvironment 查询 → 序列化 roundtrip
"""

from __future__ import annotations

import numpy as np
import pytest

from app.tree.index import (
    IndexMeta, TreeIndex, L1Node, L1Card,
    L2Node, L2Card, L3Node, L3Card,
)
from app.tree.verify import verify_tree
from app.tree.subtitle import SRTEntry, assign_subtitles_voronoi
from app.tree.environment import TreeEnvironment


class TestTreeModuleE2E:
    def test_verify_subtitle_environment_pipeline(self, tmp_path):
        """完整流程：构造树 → verify（删除幻觉实体）→ subtitle 注入 → environment 查询 → 序列化 roundtrip。"""
        # 构造一棵树，L2 有混合实体（有出处/无出处）
        l3_0 = L3Node(
            id="vid_L1_000_L2_000_L3_000",
            card=L3Card(
                frame_summary="运动员在跑步冲刺",
                visible_entities=["运动员", "跑道"],
                ongoing_actions=["跑步"],
                visible_text=["Nike", "2024"],
                spatial_layout="居中构图",
                visual_attributes={"lighting": "明亮", "camera_angle": "侧面"},
            ),
            timestamp=2.0,
            frame_path="frames/L1_000_L2_000_L3_000.jpg",
        )
        l3_1 = L3Node(
            id="vid_L1_000_L2_000_L3_001",
            card=L3Card(
                frame_summary="观众在看台上欢呼",
                visible_entities=["观众", "看台"],
                ongoing_actions=["欢呼"],
                visible_text=["Stadium"],
                spatial_layout="广角",
                visual_attributes={},
            ),
            timestamp=6.0,
            frame_path="frames/L1_000_L2_000_L3_001.jpg",
        )
        l2 = L2Node(
            id="vid_L1_000_L2_000",
            card=L2Card(
                event_description="百米决赛片段",
                entities=["运动员", "裁判", "幻觉实体XYZ"],
                actions=["跑步", "欢呼"],
                action_subjects=["运动员", "观众"],
                visible_text=["Nike", "不存在的文字ABC"],
                spatial_relations="运动员在跑道中央",
                state_changes=None,
            ),
            time_range=(0.0, 10.0),
            children=[l3_0, l3_1],
        )
        l1 = L1Node(
            id="vid_L1_000",
            card=L1Card(
                scene_summary="百米短跑决赛",
                main_setting="体育场",
                key_entities=["运动员", "不存在的人物"],
                main_actions=["比赛"],
                topic_keywords=["体育", "短跑"],
                visible_text=["Nike", "Ghost文字"],
                temporal_flow="从起跑到冲刺",
            ),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(
            metadata=IndexMeta(source_path="/test/video.mp4", modality="video"),
            roots=[l1],
        )

        # Step 1: verify — 删除无出处的实体和 visible_text
        stats = verify_tree(index)
        assert "幻觉实体XYZ" not in index.roots[0].children[0].card.entities
        assert "不存在的文字ABC" not in index.roots[0].children[0].card.visible_text
        assert "Ghost文字" not in index.roots[0].card.visible_text
        assert "不存在的人物" not in index.roots[0].card.key_entities
        # 有出处的保留
        assert "运动员" in index.roots[0].children[0].card.entities
        assert "Nike" in index.roots[0].children[0].card.visible_text

        # Step 2: subtitle 注入
        srt_entries = [
            SRTEntry(start=1.0, end=3.0, text="And the runner sprints ahead!"),
            SRTEntry(start=5.0, end=7.0, text="The crowd goes wild!"),
        ]
        assign_subtitles_voronoi(index, srt_entries)
        assert l3_0.card.subtitle != ""
        assert "sprints" in l3_0.card.subtitle
        assert l3_1.card.subtitle != ""
        assert "crowd" in l3_1.card.subtitle

        # Step 3: TreeEnvironment 查询
        env = TreeEnvironment(index)

        # view_node L3
        l3_view = env.view_node("vid_L1_000_L2_000_L3_000")
        assert "运动员在跑步冲刺" in l3_view

        # view_node L2 (should list children)
        l2_view = env.view_node("vid_L1_000_L2_000")
        assert "百米决赛片段" in l2_view
        assert "vid_L1_000_L2_000_L3_000" in l2_view

        # view_node with anchor
        anchored = env.view_node("vid_L1_000_L2_000_L3_000", anchor=True)
        assert "[c" in anchored

        # get_subtitle
        assert "sprints" in env.get_subtitle("vid_L1_000_L2_000_L3_000")

        # search_similar (with embedding)
        def fake_embed(texts):
            if isinstance(texts, str):
                texts = [texts]
            rng = np.random.RandomState(42)
            return rng.randn(len(texts), 4).astype(np.float32)

        index.embed_all(fake_embed, "test-model", 4)
        results = env.search_similar("运动员跑步", top_k=3, embed_fn=fake_embed)
        assert len(results) > 0

        # Step 4: 序列化 roundtrip
        path = tmp_path / "tree.json"
        index.save_json(str(path))
        loaded = TreeIndex.load_json(str(path))

        assert len(loaded.roots) == 1
        assert loaded.roots[0].card.scene_summary == "百米短跑决赛"
        assert loaded.roots[0].children[0].children[0].card.subtitle != ""
        assert "sprints" in loaded.roots[0].children[0].children[0].card.subtitle
        # verify 的修改也被保留
        assert "幻觉实体XYZ" not in loaded.roots[0].children[0].card.entities

    def test_repair_detector_on_broken_tree(self):
        """修复检测器能识别空卡片节点。"""
        from app.tree.repair.detector import detect_issues

        l3 = L3Node(
            id="vid_L1_000_L2_000_L3_000",
            card=L3Card("", [], [], [], "", {}),  # empty frame_summary
            timestamp=1.0,
        )
        l2 = L2Node(
            id="vid_L1_000_L2_000",
            card=L2Card("事件", [], [], [], [], "", None),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="vid_L1_000",
            card=L1Card("场景", "", [], [], [], [], ""),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])

        issues = detect_issues(index)
        assert len(issues) >= 1
        assert any(i.issue_type == "empty_field" for i in issues)
