"""XFade 转场滤镜构建领域模型单元测试."""

from __future__ import annotations

import pytest

from packages.domain.xfade_builder import (
    DEFAULT_TRANSITION_DURATION,
    SUPPORTED_TRANSITIONS,
    XFADE_TRANSITION_MAP,
    XFade_TRANSITION_NAMES,
    build_xfade_filter_chain,
    chain_filters,
    resolve_xfade_transition,
)

# ── 常量测试 ─────────────────────────────────────────────────────────────────


class TestConstants:
    def test_default_transition_duration(self):
        assert DEFAULT_TRANSITION_DURATION == 0.5

    def test_xfade_transition_map_not_empty(self):
        assert len(XFADE_TRANSITION_MAP) > 0

    def test_supported_transitions(self):
        assert "fade" in SUPPORTED_TRANSITIONS
        assert "dissolve" in SUPPORTED_TRANSITIONS

    def test_xfade_transition_names(self):
        assert "fade" in XFade_TRANSITION_NAMES
        assert "dissolve" in XFade_TRANSITION_NAMES


# ── chain_filters 测试 ──────────────────────────────────────────────────────


class TestChainFilters:
    def test_single_filter(self):
        result = chain_filters(["scale=1280:720"], "v0")
        assert result == "[0:v]scale=1280:720[v0]"

    def test_multiple_filters(self):
        result = chain_filters(["scale=1280:720", "fps=25"], "v0")
        assert result == "[0:v]scale=1280:720,fps=25[v0]"

    def test_empty_filters(self):
        result = chain_filters([], "out")
        assert result == "[0:v][out]"

    def test_custom_input_label(self):
        result = chain_filters(["scale=640:480"], "out", input_label="1:v")
        assert result == "[1:v]scale=640:480[out]"

    def test_three_filters(self):
        result = chain_filters(["trim=0:5", "setpts=PTS-STARTPTS", "fps=30"], "v1")
        assert result == "[0:v]trim=0:5,setpts=PTS-STARTPTS,fps=30[v1]"


# ── resolve_xfade_transition 测试 ───────────────────────────────────────────


class TestResolveXfadeTransition:
    def test_fade(self):
        assert resolve_xfade_transition("fade") == "fade"

    def test_dissolve(self):
        assert resolve_xfade_transition("dissolve") == "dissolve"

    def test_crossfade_maps_to_dissolve(self):
        assert resolve_xfade_transition("crossfade") == "dissolve"

    def test_slideleft(self):
        assert resolve_xfade_transition("slideleft") == "slideleft"

    def test_slide_left_maps_to_slideleft(self):
        assert resolve_xfade_transition("slide_left") == "slideleft"

    def test_slide_default_left(self):
        assert resolve_xfade_transition("slide") == "slideleft"

    def test_slideup(self):
        assert resolve_xfade_transition("slideup") == "slideup"

    def test_zoom_maps_to_zoomin(self):
        assert resolve_xfade_transition("zoom") == "zoomin"

    def test_zoomin(self):
        assert resolve_xfade_transition("zoomin") == "zoomin"

    def test_wipe_default_left(self):
        assert resolve_xfade_transition("wipe") == "wipeleft"

    def test_wipeup(self):
        assert resolve_xfade_transition("wipeup") == "wipeup"

    def test_circle_maps_to_circlecrop(self):
        assert resolve_xfade_transition("circle") == "circlecrop"

    def test_rect_maps_to_rectcrop(self):
        assert resolve_xfade_transition("rect") == "rectcrop"

    def test_unknown_falls_back_to_fade(self):
        assert resolve_xfade_transition("nonexistent_effect") == "fade"

    def test_empty_string_falls_back_to_fade(self):
        assert resolve_xfade_transition("") == "fade"

    def test_enum_with_value_attribute(self):
        """测试带 .value 属性的枚举对象."""

        class FakeEnum:
            def __init__(self, val):
                self.value = val

        assert resolve_xfade_transition(FakeEnum("fade")) == "fade"
        assert resolve_xfade_transition(FakeEnum("slideleft")) == "slideleft"
        assert resolve_xfade_transition(FakeEnum("unknown")) == "fade"


# ── build_xfade_filter_chain 测试 ───────────────────────────────────────────


class TestBuildXfadeFilterChain:
    # ── 边界情况 ──────────────────────────────────────────────────────

    def test_empty_clips(self):
        result, duration = build_xfade_filter_chain([], [], [])
        assert result == ""
        assert duration == 0.0

    def test_single_clip(self):
        result, duration = build_xfade_filter_chain([10.0], ["v0"], ["none"])
        assert "copy" in result
        assert "[v0]copy[outv]" in result
        assert duration == 10.0

    def test_single_clip_custom_output_label(self):
        result, duration = build_xfade_filter_chain([5.0], ["a0"], ["none"], output_label="final")
        assert "[a0]copy[final]" in result
        assert duration == 5.0

    # ── 两片段基础测试 ────────────────────────────────────────────────

    def test_two_clips_basic(self):
        result, duration = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "xfade=transition=fade" in result
        assert "[v0][v1]" in result
        assert "[outv]" in result
        # 总时长 = 10 + 10 - 0.5 = 19.5
        assert abs(duration - 19.5) < 0.01

    def test_two_clips_custom_duration(self):
        result, duration = build_xfade_filter_chain(
            [10.0, 10.0],
            ["v0", "v1"],
            ["none", "fade"],
            transition_duration=1.0,
        )
        assert "duration=1.000" in result
        # 总时长 = 10 + 10 - 1.0 = 19.0
        assert abs(duration - 19.0) < 0.01

    def test_two_clips_offset(self):
        """两片段时 offset 应该为 0（cumulative - td * 1 = 10 - 0.5 = 9.5？不对）。

        对于两个片段：
        - cumulative = clip_durations[0] = 10.0
        - offset = max(0, cumulative - td * i) = max(0, 10.0 - 0.5 * 1) = 9.5
        - duration=0.5, offset=9.5
        """
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "offset=9.500" in result

    # ── 多片段测试 ────────────────────────────────────────────────────

    def test_three_clips(self):
        result, duration = build_xfade_filter_chain(
            [5.0, 5.0, 5.0],
            ["v0", "v1", "v2"],
            ["none", "fade", "dissolve"],
        )
        # 应该有两个 xfade
        assert result.count("xfade=") == 2
        # 第一个 xfade 输出标签 xf1，第二个 xfade 输出 outv
        assert "xf1" in result
        assert "[outv]" in result
        # 总时长 ≈ 5 + 5 + 5 - 0.5 - 0.5 = 14.0
        assert abs(duration - 14.0) < 0.1

    def test_five_clips(self):
        result, duration = build_xfade_filter_chain(
            [3.0, 3.0, 3.0, 3.0, 3.0],
            ["v0", "v1", "v2", "v3", "v4"],
            ["none", "fade", "fade", "fade", "fade"],
        )
        assert result.count("xfade=") == 4
        # 总时长 ≈ 15 - 4 * 0.5 = 13.0
        assert abs(duration - 13.0) < 0.2

    # ── 转场效果测试 ──────────────────────────────────────────────────

    def test_dissolve_transition(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "dissolve"])
        assert "transition=dissolve" in result

    def test_slideleft_transition(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "slideleft"])
        assert "transition=slideleft" in result

    def test_cut_uses_fade(self):
        """cut 转场效果应该回退到 fade."""
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "cut"])
        # cut 不是 XFADE_TRANSITION_MAP 的键，所以会回退到 fade
        assert "transition=fade" in result

    def test_transitions_shorter_than_clips(self):
        """如果 transitions 列表比 clips 短，剩余的用 'cut'（→ fade）."""
        result, _ = build_xfade_filter_chain(
            [5.0, 5.0, 5.0],
            ["v0", "v1", "v2"],
            ["none"],  # 只有一个
        )
        # 第二个转场（index 2）会回退到 cut → fade
        assert result.count("transition=fade") == 2

    # ── 时长钳制测试 ──────────────────────────────────────────────────

    def test_short_first_clip_truncates_transition(self):
        """第一个片段非常短，转场时长应该被钳制."""
        result, duration = build_xfade_filter_chain(
            [0.3, 10.0],
            ["v0", "v1"],
            ["none", "fade"],
            transition_duration=1.0,
        )
        # offset = max(0, 0.3 - 1.0 * 1) = 0.0
        # available = max(0, 0.3 - 0.0) = 0.3
        # safe_td = min(1.0, 0.3, 剩余 10.0, clip_durations[1] 10.0) = 0.3
        assert "duration=0.300" in result
        assert abs(duration - 10.0) < 0.01  # 0.3 + 10.0 - 0.3 = 10.0

    def test_zero_duration_clips(self):
        """零时长片段的边界情况."""
        result, duration = build_xfade_filter_chain([0.0, 5.0], ["v0", "v1"], ["none", "fade"])
        # 第一个片段 0 时长，转场时长应该被钳制到最小值 0.001
        # offset = max(0, 0 - 0.5) = 0
        # available = max(0, 0 - 0) = 0
        # safe_td = min(0.5, 0, ...) = min(0.5, 0, 5.0, 5.0) = 0 → max(0.001, 0) = 0.001
        assert "duration=0.001" in result

    def test_very_long_transition_duration(self):
        """转场时长超过所有片段时长."""
        result, duration = build_xfade_filter_chain(
            [2.0, 2.0],
            ["v0", "v1"],
            ["none", "fade"],
            transition_duration=5.0,
        )
        # offset = max(0, 2.0 - 5.0) = 0
        # available = max(0, 2.0 - 0) = 2.0
        # safe_td = min(5.0, 2.0, 剩余 2.0, 2.0) = 2.0
        assert "duration=2.000" in result
        assert abs(duration - 2.0) < 0.01  # 2 + 2 - 2 = 2

    # ── 标签测试 ──────────────────────────────────────────────────────

    def test_custom_labels(self):
        result, _ = build_xfade_filter_chain(
            [10.0, 10.0],
            ["clip_a", "clip_b"],
            ["none", "fade"],
            output_label="final_v",
        )
        assert "[clip_a][clip_b]" in result
        assert "[final_v]" in result

    def test_intermediate_labels_three_clips(self):
        result, _ = build_xfade_filter_chain([5.0, 5.0, 5.0], ["v0", "v1", "v2"], ["none", "fade", "fade"])
        # 第一个 xfade 输出 xf1
        assert "[xf1][v2]" in result or result.count("[xf1]") >= 1

    # ── 总时长计算验证 ────────────────────────────────────────────────

    def test_total_duration_two_equal_clips(self):
        _, duration = build_xfade_filter_chain([8.0, 8.0], ["v0", "v1"], ["none", "fade"])
        # 8 + 8 - 0.5 = 15.5
        assert abs(duration - 15.5) < 0.01

    def test_total_duration_no_transition_impossible(self):
        """即使 transition_duration=0，也有最小 0.001 的钳制."""
        _, duration = build_xfade_filter_chain(
            [10.0, 10.0],
            ["v0", "v1"],
            ["none", "fade"],
            transition_duration=0.0,
        )
        # transition_duration=0，但 safe_td 有下限 0.001
        assert duration < 20.0  # 应该小于 20（有重叠）
        assert duration > 19.9  # 但接近 20

    # ── 滤镜字符串格式验证 ────────────────────────────────────────────

    def test_filter_format_contains_xfade_keyword(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "xfade=" in result

    def test_filter_uses_semicolon_separator(self):
        """多步 xfade 之间用分号分隔."""
        result, _ = build_xfade_filter_chain([5.0, 5.0, 5.0], ["v0", "v1", "v2"], ["none", "fade", "fade"])
        assert ";" in result
        # 3个片段 → 2个xfade → 1个分号
        assert result.count("xfade=") == 2
        assert result.count(";") == 1

    def test_filter_has_transition_param(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "transition=fade" in result

    def test_filter_has_duration_param(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "duration=" in result

    def test_filter_has_offset_param(self):
        result, _ = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", "fade"])
        assert "offset=" in result

    # ── 各种转场效果遍历测试 ──────────────────────────────────────────

    @pytest.mark.parametrize(
        "transition_name",
        list(XFADE_TRANSITION_MAP.keys()),
    )
    def test_all_supported_transitions(self, transition_name):
        """所有支持的转场效果都应该能正确生成滤镜."""
        result, duration = build_xfade_filter_chain([10.0, 10.0], ["v0", "v1"], ["none", transition_name])
        expected = XFADE_TRANSITION_MAP[transition_name]
        assert f"transition={expected}" in result
        assert duration > 0

    # ── 四片段复杂场景 ────────────────────────────────────────────────

    def test_four_clips_different_durations(self):
        durations = [3.0, 5.0, 2.0, 7.0]
        result, duration = build_xfade_filter_chain(
            durations,
            ["v0", "v1", "v2", "v3"],
            ["none", "fade", "dissolve", "slideleft"],
        )
        assert result.count("xfade=") == 3
        # 总时长 = sum(durations) - 3 * 0.5 ≈ 17 - 1.5 = 15.5
        assert abs(duration - 15.5) < 0.2

    # ── transition_duration = 0 的边界 ───────────────────────────────

    def test_zero_transition_duration_minimum_clamped(self):
        result, _ = build_xfade_filter_chain(
            [10.0, 10.0],
            ["v0", "v1"],
            ["none", "fade"],
            transition_duration=0.0,
        )
        # 至少 0.001
        assert "duration=0.001" in result

    # ── 单片段自定义输出标签 ─────────────────────────────────────────

    def test_single_clip_output_label(self):
        result, _ = build_xfade_filter_chain([5.0], ["v0"], ["none"], output_label="result")
        assert "[v0]copy[result]" in result
