"""绿幕抠像 + 音频降噪引擎 单元测试."""

from __future__ import annotations

import pytest
from video_processing.chroma_key_engine import (
    CHROMA_KEY_PRESETS,
    ChromaKeyConfig,
    ChromaKeyEngine,
    apply_chroma_key_if_needed,
)
from video_processing.noise_reduction_engine import (
    NoiseReductionConfig,
    NoiseReductionEngine,
    NoiseReductionLevel,
    apply_noise_reduction_if_needed,
)

# ═══════════════════════════════════════════════════════════════
#  ChromaKeyConfig 测试
# ═══════════════════════════════════════════════════════════════


class TestChromaKeyConfig:
    """绿幕抠像配置测试."""

    def test_default_disabled(self):
        """默认配置是禁用的."""
        config = ChromaKeyConfig()
        assert config.enabled is False
        assert config.has_effect() is False

    def test_from_dict_none(self):
        """传入 None 返回禁用配置."""
        config = ChromaKeyConfig.from_dict(None)
        assert config.enabled is False
        assert config.has_effect() is False

    def test_from_dict_empty(self):
        """传入空 dict 返回禁用配置."""
        config = ChromaKeyConfig.from_dict({})
        assert config.enabled is False

    def test_from_dict_disabled(self):
        """enabled=false 时禁用."""
        config = ChromaKeyConfig.from_dict({"enabled": False})
        assert config.enabled is False
        assert config.has_effect() is False

    def test_from_dict_enabled_defaults(self):
        """只开启，使用默认参数."""
        config = ChromaKeyConfig.from_dict({"enabled": True})
        assert config.enabled is True
        assert config.key_color == "#00FF00"
        assert config.similarity == 0.3
        assert config.blend == 0.1
        assert config.spill_suppress == 0.0
        assert config.has_effect() is True

    def test_from_dict_custom_params(self):
        """自定义所有参数."""
        config = ChromaKeyConfig.from_dict(
            {
                "enabled": True,
                "key_color": "#0000FF",
                "similarity": 0.5,
                "blend": 0.2,
                "spill_suppress": 0.3,
            }
        )
        assert config.key_color == "#0000FF"
        assert config.similarity == 0.5
        assert config.blend == 0.2
        assert config.spill_suppress == 0.3

    def test_similarity_clamp(self):
        """similarity 越界自动钳制."""
        # 低于最小值
        config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": 0})
        assert config.similarity == 0.01
        # 高于最大值
        config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": 2.0})
        assert config.similarity == 1.0

    def test_blend_clamp(self):
        """blend 越界自动钳制."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "blend": -0.5})
        assert config.blend == 0.0
        config = ChromaKeyConfig.from_dict({"enabled": True, "blend": 2.0})
        assert config.blend == 1.0

    def test_spill_suppress_clamp(self):
        """spill_suppress 越界自动钳制."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "spill_suppress": -0.1})
        assert config.spill_suppress == 0.0
        config = ChromaKeyConfig.from_dict({"enabled": True, "spill_suppress": 2.0})
        assert config.spill_suppress == 1.0

    def test_invalid_similarity_still_works(self):
        """无效相似度值也能安全解析（钳制后仍有效果）."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": "invalid"})
        # 字符串转 float 会失败 → 应该用 try/except 保护
        # 实际上 from_dict 直接 float() 转换会抛异常
        # 这里测试调用方的降级策略

    def test_has_effect_zero_similarity(self):
        """similarity 为 0（被钳制到0.01）时仍然有效果."""
        config = ChromaKeyConfig(enabled=True, similarity=0.0)
        # 注意：直接构造不走 from_dict 的钳制逻辑
        assert config.similarity == 0.0
        assert config.has_effect() is False  # similarity > 0


# ═══════════════════════════════════════════════════════════════
#  ChromaKeyEngine 测试
# ═══════════════════════════════════════════════════════════════


class TestChromaKeyEngine:
    """绿幕抠像引擎测试."""

    def test_build_filter_basic(self):
        """基础抠像滤镜构建."""
        config = ChromaKeyConfig(enabled=True, key_color="#00FF00", similarity=0.3, blend=0.1)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[0:v]", "[out]")
        assert "[0:v]" in result
        assert "[out]" in result
        assert "colorkey" in result
        assert "color=0x00FF00" in result
        assert "similarity=0.3" in result
        assert "blend=0.1" in result

    def test_build_filter_no_effect(self):
        """无效果时返回 copy."""
        config = ChromaKeyConfig(enabled=False)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "copy" in result
        assert "colorkey" not in result

    def test_normalize_color_hex(self):
        """hex 颜色格式化."""
        engine = ChromaKeyEngine(ChromaKeyConfig(enabled=True))
        assert engine._normalize_color("#00FF00") == "0x00FF00"
        assert engine._normalize_color("#00ff00") == "0x00FF00"
        assert engine._normalize_color("0x00FF00") == "0X00FF00"

    def test_normalize_color_name(self):
        """颜色名直接透传."""
        engine = ChromaKeyEngine(ChromaKeyConfig(enabled=True))
        assert engine._normalize_color("green") == "green"
        assert engine._normalize_color("blue") == "blue"

    def test_build_filter_with_spill_suppress(self):
        """溢色抑制时增加 colorchannelmixer."""
        config = ChromaKeyConfig(enabled=True, key_color="#00FF00", similarity=0.3, blend=0.1, spill_suppress=0.5)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[v0]", "[v1]")
        assert "colorkey" in result
        assert "colorchannelmixer" in result
        # 绿通道增益应该降低
        assert "gg=" in result

    def test_build_filter_no_spill_suppress(self):
        """无溢色抑制时不含 colorchannelmixer."""
        config = ChromaKeyConfig(enabled=True, key_color="#00FF00", similarity=0.3, blend=0.1, spill_suppress=0.0)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[v0]", "[v1]")
        assert "colorkey" in result
        assert "colorchannelmixer" not in result

    def test_build_filter_chromakey(self):
        """chromakey 滤镜构建（高级版本）."""
        config = ChromaKeyConfig(enabled=True, key_color="#00FF00", similarity=0.3, blend=0.1)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter_chromakey("[in]", "[out]")
        assert "chromakey" in result
        assert "color=0x00FF00" in result

    def test_blue_screen(self):
        """蓝幕抠像."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "key_color": "#0000FF", "similarity": 0.3})
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[0:v]", "[out]")
        assert "color=0x0000FF" in result


# ═══════════════════════════════════════════════════════════════
#  预设测试
# ═══════════════════════════════════════════════════════════════


class TestChromaKeyPresets:
    """绿幕预设测试."""

    def test_presets_exist(self):
        """预设列表包含常见预设."""
        assert "green_screen" in CHROMA_KEY_PRESETS
        assert "blue_screen" in CHROMA_KEY_PRESETS
        assert "red_screen" in CHROMA_KEY_PRESETS
        assert "precise_green" in CHROMA_KEY_PRESETS
        assert "soft_green" in CHROMA_KEY_PRESETS

    def test_green_screen_preset_valid(self):
        """绿幕预设参数有效."""
        preset = CHROMA_KEY_PRESETS["green_screen"]
        config = ChromaKeyConfig.from_dict({"enabled": True, **preset})
        assert config.has_effect() is True
        assert config.key_color == "#00FF00"
        assert 0.01 <= config.similarity <= 1.0

    def test_blue_screen_preset_valid(self):
        """蓝幕预设参数有效."""
        preset = CHROMA_KEY_PRESETS["blue_screen"]
        config = ChromaKeyConfig.from_dict({"enabled": True, **preset})
        assert config.key_color == "#0000FF"


# ═══════════════════════════════════════════════════════════════
#  apply_chroma_key_if_needed 测试
# ═══════════════════════════════════════════════════════════════


class TestApplyChromaKeyIfNeeded:
    """便捷函数测试."""

    def test_no_chroma_key_in_config(self):
        """没有 chroma_key 配置时返回 None."""
        result = apply_chroma_key_if_needed({}, "[in]", "[out]")
        assert result is None

    def test_disabled_chroma_key(self):
        """禁用的抠像配置返回 None."""
        result = apply_chroma_key_if_needed({"chroma_key": {"enabled": False}}, "[in]", "[out]")
        assert result is None

    def test_enabled_chroma_key(self):
        """启用的抠像配置返回滤镜字符串."""
        result = apply_chroma_key_if_needed(
            {"chroma_key": {"enabled": True, "key_color": "#00FF00"}},
            "[v0]",
            "[ck0]",
        )
        assert result is not None
        assert "colorkey" in result
        assert "[v0]" in result
        assert "[ck0]" in result

    def test_invalid_config_degrades_gracefully(self):
        """无效配置不抛出异常，返回 None."""
        result = apply_chroma_key_if_needed(
            {"chroma_key": {"enabled": True, "similarity": "invalid"}},
            "[in]",
            "[out]",
        )
        # float("invalid") 会抛 ValueError，但 apply 函数应该捕获
        # 注意：当前 from_dict 没有 try/except，调用方的 apply 应该处理
        # 这里验证不会崩溃
        assert result is None or isinstance(result, str)


# ═══════════════════════════════════════════════════════════════
#  NoiseReductionConfig 测试
# ═══════════════════════════════════════════════════════════════


class TestNoiseReductionConfig:
    """音频降噪配置测试."""

    def test_default_disabled(self):
        """默认配置是禁用的."""
        config = NoiseReductionConfig()
        assert config.enabled is False
        assert config.has_effect() is False

    def test_from_dict_none(self):
        """传入 None 返回禁用配置."""
        config = NoiseReductionConfig.from_dict(None)
        assert config.enabled is False
        assert config.has_effect() is False

    def test_from_dict_empty(self):
        """传入空 dict 返回禁用配置."""
        config = NoiseReductionConfig.from_dict({})
        assert config.enabled is False

    def test_from_dict_enabled_default(self):
        """只开启，使用默认参数."""
        config = NoiseReductionConfig.from_dict({"enabled": True})
        assert config.enabled is True
        assert config.level == NoiseReductionLevel.MEDIUM
        assert config.has_effect() is True

    def test_from_dict_low_level(self):
        """低降噪等级."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "low"})
        assert config.level == NoiseReductionLevel.LOW
        assert config.get_effective_noise_floor() == -35.0

    def test_from_dict_medium_level(self):
        """中降噪等级."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "medium"})
        assert config.level == NoiseReductionLevel.MEDIUM
        assert config.get_effective_noise_floor() == -25.0

    def test_from_dict_high_level(self):
        """高降噪等级."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "high"})
        assert config.level == NoiseReductionLevel.HIGH
        assert config.get_effective_noise_floor() == -15.0

    def test_from_dict_custom_level(self):
        """自定义降噪等级."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "custom", "noise_floor": -30.0})
        assert config.level == NoiseReductionLevel.CUSTOM
        assert config.get_effective_noise_floor() == -30.0

    def test_invalid_level_falls_back_to_medium(self):
        """无效等级回退到 medium."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "ultra"})
        assert config.level == NoiseReductionLevel.MEDIUM

    def test_noise_floor_clamp(self):
        """noise_floor 越界自动钳制."""
        # 低于最小值
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "custom", "noise_floor": -100})
        assert config.noise_floor == -60.0
        # 高于最大值
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "custom", "noise_floor": 0})
        assert config.noise_floor == -5.0

    def test_voice_enhance(self):
        """人声增强开关."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "voice_enhance": True})
        assert config.voice_enhance is True


# ═══════════════════════════════════════════════════════════════
#  NoiseReductionEngine 测试
# ═══════════════════════════════════════════════════════════════


class TestNoiseReductionEngine:
    """音频降噪引擎测试."""

    def test_build_filter_basic(self):
        """基础降噪滤镜构建."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.MEDIUM)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[0:a]", "[out]")
        assert "[0:a]" in result
        assert "[out]" in result
        assert "afftdn" in result
        assert "nf=-25" in result

    def test_build_filter_no_effect(self):
        """无效果时返回 anull."""
        config = NoiseReductionConfig(enabled=False)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "anull" in result
        assert "afftdn" not in result

    def test_low_level(self):
        """低降噪等级参数正确."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.LOW)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "nf=-35" in result

    def test_high_level(self):
        """高降噪等级参数正确."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.HIGH)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "nf=-15" in result

    def test_custom_level(self):
        """自定义降噪等级."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.CUSTOM, noise_floor=-40.0)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "nf=-40" in result

    def test_voice_enhance_adds_filters(self):
        """人声增强增加额外滤镜."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.MEDIUM, voice_enhance=True)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "afftdn" in result
        assert "highpass" in result
        assert "acompressor" in result
        assert "loudnorm" in result

    def test_no_voice_enhance_clean(self):
        """无人声增强时只有 afftdn."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.MEDIUM, voice_enhance=False)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[in]", "[out]")
        assert "afftdn" in result
        assert "highpass" not in result
        assert "acompressor" not in result

    def test_arnndn_filter(self):
        """RNN 降噪滤镜构建."""
        config = NoiseReductionConfig(enabled=True, level=NoiseReductionLevel.MEDIUM)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter_arnndn("[in]", "[out]", "/models/rnnoise.rnnn")
        assert "arnndn" in result
        assert "m=/models/rnnoise.rnnn" in result


# ═══════════════════════════════════════════════════════════════
#  apply_noise_reduction_if_needed 测试
# ═══════════════════════════════════════════════════════════════


class TestApplyNoiseReductionIfNeeded:
    """便捷函数测试."""

    def test_none_config(self):
        """None 配置返回 None."""
        result = apply_noise_reduction_if_needed(None, "[in]", "[out]")
        assert result is None

    def test_empty_config(self):
        """空配置返回 None."""
        result = apply_noise_reduction_if_needed({}, "[in]", "[out]")
        assert result is None

    def test_disabled_config(self):
        """禁用配置返回 None."""
        result = apply_noise_reduction_if_needed({"enabled": False}, "[in]", "[out]")
        assert result is None

    def test_enabled_config(self):
        """启用配置返回滤镜字符串."""
        result = apply_noise_reduction_if_needed({"enabled": True, "level": "medium"}, "[a0]", "[nr0]")
        assert result is not None
        assert "afftdn" in result
        assert "[a0]" in result
        assert "[nr0]" in result

    def test_invalid_config_degrades(self):
        """无效配置不崩溃."""
        result = apply_noise_reduction_if_needed({"enabled": True, "level": 12345}, "[in]", "[out]")
        # 不抛异常，可能返回 None 或有效结果
        assert result is None or isinstance(result, str)


# ═══════════════════════════════════════════════════════════════
#  集成测试：降级策略
# ═══════════════════════════════════════════════════════════════


class TestDegradationStrategies:
    """降级策略测试."""

    def test_chroma_key_none_config_safe(self):
        """绿幕：None 配置安全."""
        # None
        assert apply_chroma_key_if_needed(None, "[in]", "[out]") is None  # type: ignore
        # 空 dict
        assert apply_chroma_key_if_needed({}, "[in]", "[out]") is None

    def test_noise_reduction_none_config_safe(self):
        """降噪：None 配置安全."""
        assert apply_noise_reduction_if_needed(None, "[in]", "[out]") is None
        assert apply_noise_reduction_if_needed({}, "[in]", "[out]") is None

    def test_chroma_key_engine_no_effect_passthrough(self):
        """绿幕：无效果时直通 copy."""
        config = ChromaKeyConfig(enabled=False)
        engine = ChromaKeyEngine(config)
        result = engine.build_filter("[v0]", "[v1]")
        # copy 滤镜，不改变像素
        assert "copy" in result

    def test_noise_reduction_no_effect_passthrough(self):
        """降噪：无效果时直通 anull."""
        config = NoiseReductionConfig(enabled=False)
        engine = NoiseReductionEngine(config)
        result = engine.build_filter("[a0]", "[a1]")
        # anull 滤镜，不改变音频
        assert "anull" in result


# ═══════════════════════════════════════════════════════════════
#  参数边界测试
# ═══════════════════════════════════════════════════════════════


class TestParameterBoundaries:
    """参数边界测试."""

    @pytest.mark.parametrize(
        "similarity,expected",
        [
            (0.0, 0.01),  # 低于最小值 → 钳制到 min
            (0.01, 0.01),  # 最小值
            (0.5, 0.5),  # 中间值
            (1.0, 1.0),  # 最大值
            (2.0, 1.0),  # 超过最大值 → 钳制到 max
        ],
    )
    def test_similarity_boundaries(self, similarity, expected):
        """similarity 边界值测试."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "similarity": similarity})
        assert abs(config.similarity - expected) < 0.001

    @pytest.mark.parametrize(
        "blend,expected",
        [
            (-1.0, 0.0),
            (0.0, 0.0),
            (0.5, 0.5),
            (1.0, 1.0),
            (2.0, 1.0),
        ],
    )
    def test_blend_boundaries(self, blend, expected):
        """blend 边界值测试."""
        config = ChromaKeyConfig.from_dict({"enabled": True, "blend": blend})
        assert abs(config.blend - expected) < 0.001

    @pytest.mark.parametrize(
        "noise_floor,expected",
        [
            (-100, -60.0),
            (-60, -60.0),
            (-30, -30.0),
            (-5, -5.0),
            (0, -5.0),
        ],
    )
    def test_noise_floor_boundaries(self, noise_floor, expected):
        """noise_floor 边界值测试."""
        config = NoiseReductionConfig.from_dict({"enabled": True, "level": "custom", "noise_floor": noise_floor})
        assert abs(config.noise_floor - expected) < 0.001
