from dataclasses import FrozenInstanceError

"""filter_presets 领域层单元测试 - 滤镜预设库"""

import pytest

from packages.domain.filter_presets import (
    FILTER_PRESET_LIBRARY,
    FilterPreset,
    build_ffmpeg_filter,
    get_filter_preset,
    list_filter_presets,
)


class TestFilterPreset:
    """FilterPreset 数据类测试"""

    def test_create_minimal(self):
        preset = FilterPreset(id="test", name="测试", category="basic")
        assert preset.id == "test"
        assert preset.name == "测试"
        assert preset.category == "basic"
        assert preset.description == ""
        assert preset.tags == []
        assert preset.brightness == 0.0
        assert preset.contrast == 1.0
        assert preset.saturation == 1.0
        assert preset.gamma == 1.0
        assert preset.gamma_r == 1.0
        assert preset.gamma_g == 1.0
        assert preset.gamma_b == 1.0
        assert preset.hue == 0.0
        assert preset.lut_url == ""

    def test_create_with_all_params(self):
        preset = FilterPreset(
            id="custom",
            name="自定义",
            category="cinematic",
            description="电影感调色",
            tags=["电影", "调色"],
            brightness=0.1,
            contrast=1.2,
            saturation=0.9,
            gamma=1.1,
            gamma_r=1.05,
            gamma_g=1.0,
            gamma_b=0.95,
            hue=10.0,
            lut_url="http://example.com/lut.png",
        )
        assert preset.category == "cinematic"
        assert preset.description == "电影感调色"
        assert preset.tags == ["电影", "调色"]
        assert preset.brightness == 0.1
        assert preset.contrast == 1.2
        assert preset.lut_url == "http://example.com/lut.png"

    def test_frozen_immutable(self):
        """frozen dataclass 不可修改"""
        preset = FilterPreset(id="test", name="测试", category="basic")
        with pytest.raises(FrozenInstanceError):
            preset.name = "改名"

    def test_tags_default_empty_list(self):
        preset = FilterPreset(id="test", name="测试", category="basic")
        assert preset.tags == []
        # 每次创建独立的 list
        preset2 = FilterPreset(id="test2", name="测试2", category="basic")
        assert preset.tags is not preset2.tags


class TestFilterPresetLibrary:
    """FILTER_PRESET_LIBRARY 预设库测试"""

    def test_library_not_empty(self):
        assert len(FILTER_PRESET_LIBRARY) > 0

    def test_all_presets_have_unique_ids(self):
        """所有预设 ID 唯一"""
        ids = [p.id for p in FILTER_PRESET_LIBRARY]
        assert len(ids) == len(set(ids))

    def test_all_presets_have_name_and_category(self):
        for preset in FILTER_PRESET_LIBRARY:
            assert preset.id, f"{preset} has no id"
            assert preset.name, f"{preset.id} has no name"
            assert preset.category, f"{preset.id} has no category"

    def test_filter_none_exists(self):
        """原图预设存在"""
        none_preset = next((p for p in FILTER_PRESET_LIBRARY if p.id == "filter_none"), None)
        assert none_preset is not None
        assert none_preset.name == "原图"
        assert none_preset.category == "basic"

    def test_known_categories_exist(self):
        """已知分类都有预设"""
        categories = {p.category for p in FILTER_PRESET_LIBRARY}
        assert "basic" in categories

    def test_basic_category_presets(self):
        """基础分类至少有几个预设"""
        basic = [p for p in FILTER_PRESET_LIBRARY if p.category == "basic"]
        assert len(basic) >= 3

    def test_preset_params_in_reasonable_range(self):
        """预设参数在合理范围内"""
        for preset in FILTER_PRESET_LIBRARY:
            assert -1.0 <= preset.brightness <= 1.0, f"{preset.id} brightness out of range"
            assert 0.0 <= preset.contrast <= 2.0, f"{preset.id} contrast out of range"
            assert 0.0 <= preset.saturation <= 3.0, f"{preset.id} saturation out of range"


class TestGetFilterPreset:
    """get_filter_preset 函数测试"""

    def test_get_existing_preset(self):
        preset = get_filter_preset("filter_none")
        assert preset is not None
        assert preset.id == "filter_none"

    def test_get_nonexistent_preset(self):
        assert get_filter_preset("nonexistent_filter") is None

    def test_get_returns_correct_type(self):
        preset = get_filter_preset("filter_none")
        assert isinstance(preset, FilterPreset)


class TestListFilterPresets:
    """list_filter_presets 函数测试"""

    def test_list_all(self):
        """不带参数返回所有预设"""
        all_presets = list_filter_presets()
        assert len(all_presets) == len(FILTER_PRESET_LIBRARY)

    def test_filter_by_category(self):
        """按分类筛选"""
        basic_presets = list_filter_presets(category="basic")
        assert len(basic_presets) > 0
        assert all(p.category == "basic" for p in basic_presets)

    def test_filter_by_nonexistent_category(self):
        """不存在的分类返回空列表"""
        result = list_filter_presets(category="nonexistent_category")
        assert result == []

    def test_search_by_name(self):
        """按名称搜索"""
        result = list_filter_presets(keyword="明")
        assert len(result) >= 1
        assert any("明" in p.name for p in result)

    def test_search_by_tag(self):
        """按标签搜索"""
        # 找到有标签的预设
        tagged = [p for p in FILTER_PRESET_LIBRARY if p.tags]
        if tagged:
            tag = tagged[0].tags[0]
            result = list_filter_presets(keyword=tag)
            assert len(result) >= 1

    def test_search_case_insensitive_in_name(self):
        """搜索对中文名称有效"""
        result = list_filter_presets(keyword="原图")
        assert any(p.id == "filter_none" for p in result)

    def test_search_empty_returns_all(self):
        """空搜索返回所有"""
        result = list_filter_presets(keyword="")
        assert len(result) == len(FILTER_PRESET_LIBRARY)

    def test_combined_category_and_search(self):
        """同时按分类和搜索筛选"""
        result = list_filter_presets(category="basic", keyword="明")
        assert all(p.category == "basic" for p in result)
        if result:
            assert any("明" in p.name or any("明" in t for t in p.tags) for p in result)

    def test_returns_list_of_filterpreset(self):
        result = list_filter_presets()
        assert all(isinstance(p, FilterPreset) for p in result)


class TestBuildFfmpegFilter:
    """build_ffmpeg_filter 函数测试"""

    def test_filter_none_returns_empty_or_simple(self):
        """原图滤镜应该返回空字符串或无操作滤镜"""
        result = build_ffmpeg_filter("filter_none", 100)
        # 应该是字符串，且不包含实质性调色参数
        assert isinstance(result, str)

    def test_full_intensity(self):
        """强度 100 时应用全量参数"""
        result = build_ffmpeg_filter("filter_brighten", 100)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_zero_intensity(self):
        """强度 0 时应该是原图效果"""
        result = build_ffmpeg_filter("filter_brighten", 0)
        assert isinstance(result, str)

    def test_half_intensity(self):
        """强度 50 时参数减半"""
        result50 = build_ffmpeg_filter("filter_brighten", 50)
        result100 = build_ffmpeg_filter("filter_brighten", 100)
        # 50% 和 100% 的结果应该不同
        assert result50 != result100

    def test_nonexistent_preset(self):
        """不存在的预设返回空字符串或默认值"""
        result = build_ffmpeg_filter("nonexistent", 100)
        assert isinstance(result, str)

    def test_intensity_clamped(self):
        """强度超过 100 或低于 0 的处理"""
        result_high = build_ffmpeg_filter("filter_brighten", 150)
        result_low = build_ffmpeg_filter("filter_brighten", -10)
        assert isinstance(result_high, str)
        assert isinstance(result_low, str)

    def test_contains_eq_filter(self):
        """结果应该包含 eq 滤镜参数"""
        result = build_ffmpeg_filter("filter_brighten", 100)
        # FFmpeg eq 滤镜通常包含 brightness/contrast/saturation 等参数
        # 至少应该有滤镜相关的字符串
        assert len(result) > 0
