"""转场预设库单元测试."""

from __future__ import annotations

import dataclasses

import pytest

from packages.domain.transition_presets import (
    TRANSITION_PRESET_LIBRARY,
    TransitionPreset,
    get_default_transition,
    get_transition_preset,
    list_transition_presets,
)

# ── 数据类测试 ────────────────────────────────────────────────────────────────


class TestTransitionPreset:
    """TransitionPreset 数据类测试."""

    def test_basic_attributes(self):
        """基础属性可访问."""
        p = TransitionPreset(
            id="test_id",
            name="测试转场",
            category="fade",
            description="测试描述",
            tags=["tag1", "tag2"],
            transition="fade",
            default_duration=0.5,
            min_duration=0.1,
            max_duration=3.0,
            has_custom_params=False,
        )
        assert p.id == "test_id"
        assert p.name == "测试转场"
        assert p.category == "fade"
        assert p.description == "测试描述"
        assert p.tags == ["tag1", "tag2"]
        assert p.transition == "fade"
        assert p.default_duration == 0.5
        assert p.min_duration == 0.1
        assert p.max_duration == 3.0
        assert p.has_custom_params is False

    def test_default_values(self):
        """默认值正确."""
        p = TransitionPreset(id="t", name="T", category="basic")
        assert p.description == ""
        assert p.tags == []
        assert p.transition == "fade"
        assert p.default_duration == 0.5
        assert p.min_duration == 0.1
        assert p.max_duration == 3.0
        assert p.has_custom_params is False

    def test_frozen_immutable(self):
        """frozen dataclass 不可修改."""
        p = TransitionPreset(id="t", name="T", category="basic")
        with pytest.raises(dataclasses.FrozenInstanceError):
            p.name = "新名字"

    def test_not_hashable_due_to_list(self):
        """含list字段(tags)的frozen dataclass不可哈希（list可变）."""
        p = TransitionPreset(id="t", name="T", category="basic")
        with pytest.raises(TypeError):
            hash(p)

    def test_equality(self):
        """相同属性的实例相等."""
        p1 = TransitionPreset(id="t", name="T", category="basic")
        p2 = TransitionPreset(id="t", name="T", category="basic")
        assert p1 == p2

    def test_inequality(self):
        """不同属性的实例不等."""
        p1 = TransitionPreset(id="t1", name="T", category="basic")
        p2 = TransitionPreset(id="t2", name="T", category="basic")
        assert p1 != p2


# ── 预设库完整性测试 ──────────────────────────────────────────────────────────


class TestTransitionPresetLibrary:
    """TRANSITION_PRESET_LIBRARY 预设库完整性测试."""

    def test_library_not_empty(self):
        """预设库不为空."""
        assert len(TRANSITION_PRESET_LIBRARY) > 0

    def test_preset_count(self):
        """预设数量应大于20."""
        assert len(TRANSITION_PRESET_LIBRARY) >= 20

    def test_all_ids_unique(self):
        """所有预设ID唯一."""
        ids = [p.id for p in TRANSITION_PRESET_LIBRARY]
        assert len(ids) == len(set(ids)), f"存在重复ID: {[i for i in ids if ids.count(i) > 1]}"

    def test_all_have_required_fields(self):
        """所有预设都有必填字段."""
        for p in TRANSITION_PRESET_LIBRARY:
            assert p.id, f"预设缺少id: {p}"
            assert p.name, f"预设 {p.id} 缺少name"
            assert p.category, f"预设 {p.id} 缺少category"
            assert p.transition, f"预设 {p.id} 缺少transition"

    def test_duration_range_valid(self):
        """每个预设的时长范围合理: min <= default <= max."""
        for p in TRANSITION_PRESET_LIBRARY:
            assert (
                p.min_duration <= p.default_duration
            ), f"{p.id}: min({p.min_duration}) > default({p.default_duration})"
            assert (
                p.default_duration <= p.max_duration
            ), f"{p.id}: default({p.default_duration}) > max({p.max_duration})"

    def test_min_duration_non_negative(self):
        """最小时长不能为负."""
        for p in TRANSITION_PRESET_LIBRARY:
            assert p.min_duration >= 0, f"{p.id}: min_duration为负"

    def test_categories_are_valid(self):
        """分类都在预期集合内."""
        valid_categories = {"basic", "fade", "slide", "zoom", "warp", "special"}
        for p in TRANSITION_PRESET_LIBRARY:
            assert p.category in valid_categories, f"{p.id}: 未知分类 {p.category}"

    @pytest.mark.parametrize(
        "category,expected_min",
        [
            ("basic", 2),
            ("fade", 4),
            ("slide", 4),
            ("zoom", 2),
            ("warp", 6),
            ("special", 2),
        ],
    )
    def test_category_min_count(self, category: str, expected_min: int):
        """每个分类至少有预期数量的预设."""
        count = sum(1 for p in TRANSITION_PRESET_LIBRARY if p.category == category)
        assert count >= expected_min, f"分类 {category} 只有 {count} 个，预期至少 {expected_min}"

    def test_tags_is_list(self):
        """tags字段是列表."""
        for p in TRANSITION_PRESET_LIBRARY:
            assert isinstance(p.tags, list), f"{p.id}: tags不是列表"

    def test_none_transition_has_zero_duration(self):
        """无转场预设时长为0."""
        none_preset = get_transition_preset("transition_none")
        assert none_preset is not None
        assert none_preset.min_duration == 0.0
        assert none_preset.max_duration == 0.0
        assert none_preset.default_duration == 0.0
        assert none_preset.transition == "none"


# ── get_transition_preset 测试 ────────────────────────────────────────────────


class TestGetTransitionPreset:
    """get_transition_preset 函数测试."""

    def test_get_existing_preset(self):
        """获取存在的预设."""
        p = get_transition_preset("transition_fade")
        assert p is not None
        assert p.id == "transition_fade"
        assert p.name == "淡入淡出"
        assert p.category == "fade"

    def test_get_nonexistent_preset(self):
        """获取不存在的预设返回None."""
        p = get_transition_preset("nonexistent_id")
        assert p is None

    def test_get_none_preset(self):
        """获取无转场预设."""
        p = get_transition_preset("transition_none")
        assert p is not None
        assert p.transition == "none"

    def test_get_random_preset(self):
        """获取随机预设."""
        p = get_transition_preset("transition_random")
        assert p is not None
        assert p.transition == "random"

    def test_case_sensitive(self):
        """ID区分大小写."""
        p = get_transition_preset("TRANSITION_FADE")
        assert p is None

    def test_empty_string(self):
        """空字符串返回None."""
        p = get_transition_preset("")
        assert p is None

    def test_returns_same_instance(self):
        """多次调用返回同一个对象（库引用）."""
        p1 = get_transition_preset("transition_fade")
        p2 = get_transition_preset("transition_fade")
        assert p1 is p2

    def test_all_presets_accessible_by_id(self):
        """所有预设都可通过ID获取."""
        for p in TRANSITION_PRESET_LIBRARY:
            fetched = get_transition_preset(p.id)
            assert fetched is not None, f"无法通过ID获取: {p.id}"
            assert fetched.id == p.id


# ── list_transition_presets 测试 ──────────────────────────────────────────────


class TestListTransitionPresets:
    """list_transition_presets 函数测试."""

    def test_no_filter_returns_all(self):
        """无筛选参数返回全部预设."""
        results = list_transition_presets()
        assert len(results) == len(TRANSITION_PRESET_LIBRARY)

    def test_filter_by_category_fade(self):
        """按fade分类筛选."""
        results = list_transition_presets(category="fade")
        assert len(results) > 0
        assert all(p.category == "fade" for p in results)

    def test_filter_by_category_slide(self):
        """按slide分类筛选."""
        results = list_transition_presets(category="slide")
        assert len(results) == 4
        assert all(p.category == "slide" for p in results)

    def test_filter_by_category_basic(self):
        """按basic分类筛选."""
        results = list_transition_presets(category="basic")
        assert len(results) == 2  # none + random

    def test_filter_by_invalid_category(self):
        """无效分类返回空列表."""
        results = list_transition_presets(category="nonexistent")
        assert results == []

    def test_keyword_search_in_name(self):
        """关键词搜索name字段."""
        results = list_transition_presets(keyword="淡入淡出")
        assert len(results) >= 1
        assert any(p.id == "transition_fade" for p in results)

    def test_keyword_search_in_description(self):
        """关键词搜索description字段."""
        results = list_transition_presets(keyword="硬切")
        assert len(results) >= 1
        assert any(p.id == "transition_none" for p in results)

    def test_keyword_search_in_tags(self):
        """关键词搜索tags字段."""
        results = list_transition_presets(keyword="模糊")
        assert len(results) >= 2  # hblur + wipeblur
        ids = [p.id for p in results]
        assert "transition_hblur" in ids
        assert "transition_wipeblur" in ids

    def test_keyword_case_insensitive(self):
        """关键词搜索不区分大小写（英文）."""
        results1 = list_transition_presets(keyword="fade")
        results2 = list_transition_presets(keyword="FADE")
        assert len(results1) == len(results2)

    def test_keyword_chinese(self):
        """中文关键词搜索."""
        results = list_transition_presets(keyword="滑")
        assert len(results) >= 4  # 4个slide
        assert all("滑" in p.name for p in results)

    def test_keyword_no_match(self):
        """无匹配关键词返回空."""
        results = list_transition_presets(keyword="完全不存在的关键词xyz")
        assert results == []

    def test_keyword_empty_string(self):
        """空关键词返回全部."""
        results = list_transition_presets(keyword="")
        assert len(results) == len(TRANSITION_PRESET_LIBRARY)

    def test_category_and_keyword_combined(self):
        """分类+关键词组合筛选."""
        results = list_transition_presets(category="warp", keyword="擦除")
        assert len(results) >= 4  # 4个wipe
        assert all(p.category == "warp" for p in results)
        assert all("擦除" in p.name for p in results)

    def test_category_and_keyword_no_match(self):
        """分类+关键词不匹配返回空."""
        results = list_transition_presets(category="fade", keyword="滑动")
        assert results == []

    def test_preserve_order(self):
        """保持预设库的顺序."""
        results = list_transition_presets()
        for i, p in enumerate(TRANSITION_PRESET_LIBRARY):
            assert results[i].id == p.id

    def test_filtered_results_are_all_valid(self):
        """筛选结果的每个预设都有完整属性."""
        results = list_transition_presets(category="slide")
        for p in results:
            assert p.id
            assert p.name
            assert p.category == "slide"
            assert isinstance(p.tags, list)


# ── get_default_transition 测试 ───────────────────────────────────────────────


class TestGetDefaultTransition:
    """get_default_transition 函数测试."""

    def test_default_is_none(self):
        """默认转场是无转场."""
        p = get_default_transition()
        assert p.id == "transition_none"
        assert p.transition == "none"

    def test_default_zero_duration(self):
        """默认转场时长为0."""
        p = get_default_transition()
        assert p.default_duration == 0.0
        assert p.min_duration == 0.0
        assert p.max_duration == 0.0

    def test_default_category_basic(self):
        """默认转场属于basic分类."""
        p = get_default_transition()
        assert p.category == "basic"

    def test_default_same_instance(self):
        """多次调用返回同一实例."""
        p1 = get_default_transition()
        p2 = get_default_transition()
        assert p1 is p2

    def test_default_matches_get_preset(self):
        """默认转场与通过ID获取的一致."""
        default = get_default_transition()
        by_id = get_transition_preset("transition_none")
        assert default is by_id


# ── 预设个体属性抽样测试 ──────────────────────────────────────────────────────


class TestPresetSamples:
    """典型预设的属性验证."""

    @pytest.mark.parametrize(
        "preset_id,expected_name,expected_category,expected_transition",
        [
            ("transition_none", "无转场", "basic", "none"),
            ("transition_random", "随机", "basic", "random"),
            ("transition_fade", "淡入淡出", "fade", "fade"),
            ("transition_fadeblack", "黑场过渡", "fade", "fadeblack"),
            ("transition_fadewhite", "白场过渡", "fade", "fadewhite"),
            ("transition_slideleft", "左滑", "slide", "slideleft"),
            ("transition_slideright", "右滑", "slide", "slideright"),
            ("transition_slideup", "上滑", "slide", "slideup"),
            ("transition_slidedown", "下滑", "slide", "slidedown"),
            ("transition_zoomin", "放大进入", "zoom", "zoomin"),
            ("transition_zoomout", "缩小退出", "zoom", "zoomout"),
            ("transition_dissolve", "溶解", "warp", "dissolve"),
            ("transition_circlecrop", "圆形展开", "warp", "circlecrop"),
            ("transition_hblur", "水平模糊", "special", "hblur"),
        ],
    )
    def test_preset_attributes(
        self, preset_id: str, expected_name: str, expected_category: str, expected_transition: str
    ):
        """典型预设属性验证."""
        p = get_transition_preset(preset_id)
        assert p is not None
        assert p.name == expected_name
        assert p.category == expected_category
        assert p.transition == expected_transition

    def test_dissolve_longer_default(self):
        """溶解效果默认时长较长(0.8s)."""
        p = get_transition_preset("transition_dissolve")
        assert p is not None
        assert p.default_duration == 0.8
