"""修复检测器单元测试。"""

from __future__ import annotations

from typing import TYPE_CHECKING

from app.tree.index import (
    IndexMeta,
    L1Card,
    L1Node,
    L2Card,
    L2Node,
    L3Card,
    L3Node,
    TreeIndex,
)
from app.tree.repair.detector import NodeIssue, detect_issues

if TYPE_CHECKING:
    from pathlib import Path


def _card_l3(summary: str = "正常描述") -> L3Card:
    return L3Card(summary, ["实体"], ["动作"], [], "居中", {})


def _card_l2() -> L2Card:
    return L2Card("事件", [], [], [], [], "", None)


def _card_l1() -> L1Card:
    return L1Card("场景", "", [], [], [], [], "")


class TestDetectIssues:
    def test_healthy_tree_no_issues(self) -> None:
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(), timestamp=1.0)
        l2 = L2Node(
            id="l1_0_l2_0",
            card=_card_l2(),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        assert detect_issues(index) == []

    def test_empty_frame_summary(self) -> None:
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(""), timestamp=1.0)
        l2 = L2Node(
            id="l1_0_l2_0",
            card=_card_l2(),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert any(i.issue_type == "empty_field" and i.node_id == "l1_0_l2_0_l3_0" for i in issues)

    def test_missing_frame_file(self, tmp_path: Path) -> None:
        l3 = L3Node(
            id="l1_0_l2_0_l3_0",
            card=_card_l3(),
            timestamp=1.0,
            frame_path="frames/missing.jpg",
        )
        l2 = L2Node(
            id="l1_0_l2_0",
            card=_card_l2(),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index, frames_dir=tmp_path)
        assert any(i.issue_type == "missing_frame" for i in issues)

    def test_l2_no_children(self) -> None:
        l2 = L2Node(
            id="l1_0_l2_0",
            card=_card_l2(),
            time_range=(0.0, 10.0),
            children=[],
        )
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert any(i.issue_type == "no_children" and i.level == 2 for i in issues)

    def test_l1_no_children(self) -> None:
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 10.0),
            children=[],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert any(i.issue_type == "no_children" and i.level == 1 for i in issues)

    def test_empty_visible_entities_not_flagged(self) -> None:
        """visible_entities 为空是合法状态（静物/黑帧），不触发 empty_field。"""
        card = L3Card("正常描述", [], ["动作"], [], "居中", {})
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
        l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
        l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert not any(i.issue_type == "empty_field" for i in issues)

    def test_empty_ongoing_actions_not_flagged(self) -> None:
        """ongoing_actions 为空是合法状态（静物/黑帧），不触发 empty_field。"""
        card = L3Card("正常描述", ["实体"], [], [], "居中", {})
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
        l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
        l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert not any(i.issue_type == "empty_field" for i in issues)

    def test_empty_spatial_layout(self) -> None:
        """spatial_layout 为空也触发 empty_field。"""
        card = L3Card("正常描述", ["实体"], ["动作"], [], "", {})
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
        l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
        l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert any(i.issue_type == "empty_field" and "spatial_layout" in i.details for i in issues)

    def test_multiple_empty_fields_single_issue(self) -> None:
        """多个必填字段同时为空只产生一个 issue，details 列出所有空字段。"""
        card = L3Card("", [], [], [], "", {})
        l3 = L3Node(id="l1_0_l2_0_l3_0", card=card, timestamp=1.0)
        l2 = L2Node(id="l1_0_l2_0", card=_card_l2(), time_range=(0.0, 10.0), children=[l3])
        l1 = L1Node(id="l1_0", card=_card_l1(), time_range=(0.0, 10.0), children=[l2])
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = [i for i in detect_issues(index) if i.issue_type == "empty_field"]
        assert len(issues) == 1
        assert "frame_summary" in issues[0].details
        assert "spatial_layout" in issues[0].details

    def test_time_gap(self) -> None:
        l3_a = L3Node(id="l1_0_l2_0_l3_0", card=_card_l3(), timestamp=1.0)
        l3_b = L3Node(id="l1_0_l2_1_l3_0", card=_card_l3(), timestamp=20.0)
        l2_a = L2Node(
            id="l1_0_l2_0",
            card=_card_l2(),
            time_range=(0.0, 5.0),
            children=[l3_a],
        )
        l2_b = L2Node(
            id="l1_0_l2_1",
            card=_card_l2(),
            time_range=(15.0, 25.0),
            children=[l3_b],
        )
        l1 = L1Node(
            id="l1_0",
            card=_card_l1(),
            time_range=(0.0, 25.0),
            children=[l2_a, l2_b],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        assert any(i.issue_type == "time_gap" for i in issues)

    def test_detects_l2_empty_event_description(self) -> None:
        """L2 event_description 为空应报 empty_field。"""
        l3 = L3Node(
            id="l1_0_l2_0_l3_0",
            card=L3Card("正常帧", ["实体"], ["动作"], [], "居中", {}),
            timestamp=1.0,
        )
        l2 = L2Node(
            id="l1_0_l2_0",
            card=L2Card("", [], [], [], [], "", None),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="l1_0",
            card=L1Card("正常场景", "", [], [], [], [], ""),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        l2_empties = [i for i in issues if i.level == 2 and i.issue_type == "empty_field"]
        assert len(l2_empties) == 1
        assert "event_description" in l2_empties[0].details

    def test_detects_l1_empty_scene_summary(self) -> None:
        """L1 scene_summary 为空应报 empty_field。"""
        l3 = L3Node(
            id="l1_0_l2_0_l3_0",
            card=L3Card("正常帧", ["实体"], ["动作"], [], "居中", {}),
            timestamp=1.0,
        )
        l2 = L2Node(
            id="l1_0_l2_0",
            card=L2Card("正常事件", [], [], [], [], "", None),
            time_range=(0.0, 10.0),
            children=[l3],
        )
        l1 = L1Node(
            id="l1_0",
            card=L1Card("", "", [], [], [], [], ""),
            time_range=(0.0, 10.0),
            children=[l2],
        )
        index = TreeIndex(metadata=IndexMeta("/t.mp4", "video"), roots=[l1])
        issues = detect_issues(index)
        l1_empties = [i for i in issues if i.level == 1 and i.issue_type == "empty_field"]
        assert len(l1_empties) == 1
        assert "scene_summary" in l1_empties[0].details
