"""TTS 配音配置模型单元测试."""

from __future__ import annotations

import pytest

from packages.domain.tts_config import TtsConfig

# ── 默认值测试 ────────────────────────────────────────────────────────────────


class TestTtsConfigDefaults:
    """TtsConfig 默认值测试."""

    def test_default_enabled_false(self):
        """默认禁用配音."""
        cfg = TtsConfig()
        assert cfg.enabled is False

    def test_default_voice_id_empty(self):
        """默认空音色ID."""
        cfg = TtsConfig()
        assert cfg.voice_id == ""

    def test_default_speed(self):
        """默认语速1.0."""
        cfg = TtsConfig()
        assert cfg.speed == 1.0

    def test_default_pitch(self):
        """默认语调0."""
        cfg = TtsConfig()
        assert cfg.pitch == 0.0

    def test_default_volume(self):
        """默认音量0.8."""
        cfg = TtsConfig()
        assert cfg.volume == 0.8

    def test_default_text_empty(self):
        """默认空文本."""
        cfg = TtsConfig()
        assert cfg.text == ""

    def test_default_align_mode(self):
        """默认整段配音对齐."""
        cfg = TtsConfig()
        assert cfg.align_mode == "full"

    def test_default_overlap_mode(self):
        """默认替换原音."""
        cfg = TtsConfig()
        assert cfg.overlap_mode == "replace"


# ── parse - 基础场景测试 ─────────────────────────────────────────────────────


class TestTtsConfigParseBasic:
    """TtsConfig.parse 基础场景测试."""

    def test_none_data(self):
        """None输入返回默认配置（禁用）."""
        cfg = TtsConfig.parse(None)
        assert cfg.enabled is False

    def test_empty_dict(self):
        """空dict返回默认配置."""
        cfg = TtsConfig.parse({})
        assert cfg.enabled is False

    def test_not_dict_returns_default(self):
        """非dict输入返回默认."""
        cfg = TtsConfig.parse("not_a_dict")
        assert cfg.enabled is False
        assert cfg.speed == 1.0

    def test_disabled_returns_fast(self):
        """enabled为False时直接返回disabled配置."""
        cfg = TtsConfig.parse({"enabled": False, "voice_id": "v1", "speed": 1.5})
        assert cfg.enabled is False
        # 其他字段为默认值
        assert cfg.voice_id == ""
        assert cfg.speed == 1.0

    def test_enabled_basic(self):
        """启用配音基础配置."""
        cfg = TtsConfig.parse({"enabled": True, "voice_id": "voice_001"})
        assert cfg.enabled is True
        assert cfg.voice_id == "voice_001"

    def test_full_config(self):
        """完整配置解析."""
        cfg = TtsConfig.parse(
            {
                "enabled": True,
                "voice_id": "v_test",
                "speed": 1.2,
                "pitch": 2.5,
                "volume": 0.9,
                "text": "大家好",
                "align_mode": "subtitle",
                "overlap_mode": "mix",
            }
        )
        assert cfg.enabled is True
        assert cfg.voice_id == "v_test"
        assert cfg.speed == 1.2
        assert cfg.pitch == 2.5
        assert cfg.volume == 0.9
        assert cfg.text == "大家好"
        assert cfg.align_mode == "subtitle"
        assert cfg.overlap_mode == "mix"


# ── parse - 类型校验测试 ─────────────────────────────────────────────────────


class TestTtsConfigParseTypeChecks:
    """TtsConfig.parse 类型校验测试."""

    def test_enabled_not_bool(self):
        """enabled不是bool时视为False."""
        cfg = TtsConfig.parse({"enabled": "true", "voice_id": "v1"})
        assert cfg.enabled is False

    def test_enabled_int_treated_as_non_bool(self):
        """enabled为整数时视为非bool（Python里1是True但isinstance(1, bool)是True？）."""
        # Python里bool是int的子类，isinstance(True, int)为True
        # 反过来 isinstance(1, bool) 为 False，所以1会被当作无效值
        cfg = TtsConfig.parse({"enabled": 1, "voice_id": "v1"})
        assert cfg.enabled is False

    def test_voice_id_not_string(self):
        """voice_id不是字符串时回退到空."""
        cfg = TtsConfig.parse({"enabled": True, "voice_id": 123})
        assert cfg.voice_id == ""

    def test_speed_not_number(self):
        """speed不是数字时回退到1.0."""
        cfg = TtsConfig.parse({"enabled": True, "speed": "fast"})
        assert cfg.speed == 1.0

    def test_speed_int_accepted(self):
        """整数speed也接受."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 2})
        assert cfg.speed == 2.0

    def test_pitch_not_number(self):
        """pitch不是数字时回退到0."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": "high"})
        assert cfg.pitch == 0.0

    def test_pitch_int_accepted(self):
        """整数pitch也接受."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": 5})
        assert cfg.pitch == 5.0

    def test_volume_not_number(self):
        """volume不是数字时回退到0.8."""
        cfg = TtsConfig.parse({"enabled": True, "volume": "loud"})
        assert cfg.volume == 0.8

    def test_volume_int_accepted(self):
        """整数volume也接受."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 1})
        assert cfg.volume == 1.0

    def test_text_not_string(self):
        """text不是字符串时回退到空."""
        cfg = TtsConfig.parse({"enabled": True, "text": 12345})
        assert cfg.text == ""

    def test_text_empty_string(self):
        """空文本字符串是有效的."""
        cfg = TtsConfig.parse({"enabled": True, "text": ""})
        assert cfg.text == ""


# ── parse - 边界钳制测试 ─────────────────────────────────────────────────────


class TestTtsConfigParseClamping:
    """TtsConfig.parse 边界钳制测试."""

    # speed 边界

    def test_speed_below_min_clamped(self):
        """语速低于最小值钳制到0.5."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 0.1})
        assert cfg.speed == 0.5

    def test_speed_negative_clamped(self):
        """负语速钳制到0.5."""
        cfg = TtsConfig.parse({"enabled": True, "speed": -1.0})
        assert cfg.speed == 0.5

    def test_speed_above_max_clamped(self):
        """语速高于最大值钳制到2.0."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 5.0})
        assert cfg.speed == 2.0

    def test_speed_at_min_ok(self):
        """刚好等于最小值正常."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 0.5})
        assert cfg.speed == 0.5

    def test_speed_at_max_ok(self):
        """刚好等于最大值正常."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 2.0})
        assert cfg.speed == 2.0

    def test_speed_normal_ok(self):
        """正常范围内语速保持不变."""
        cfg = TtsConfig.parse({"enabled": True, "speed": 1.5})
        assert cfg.speed == 1.5

    # pitch 边界

    def test_pitch_below_min_clamped(self):
        """语调低于最小值钳制到-12."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": -20})
        assert cfg.pitch == -12

    def test_pitch_above_max_clamped(self):
        """语调高于最大值钳制到12."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": 20})
        assert cfg.pitch == 12

    def test_pitch_at_min_ok(self):
        """刚好等于最小值正常."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": -12})
        assert cfg.pitch == -12

    def test_pitch_at_max_ok(self):
        """刚好等于最大值正常."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": 12})
        assert cfg.pitch == 12

    def test_pitch_normal_ok(self):
        """正常范围内语调保持不变."""
        cfg = TtsConfig.parse({"enabled": True, "pitch": 3.5})
        assert cfg.pitch == 3.5

    # volume 边界

    def test_volume_below_min_clamped(self):
        """音量低于最小值钳制到0."""
        cfg = TtsConfig.parse({"enabled": True, "volume": -0.5})
        assert cfg.volume == 0.0

    def test_volume_above_max_clamped(self):
        """音量高于最大值钳制到1."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 2.0})
        assert cfg.volume == 1.0

    def test_volume_at_min_ok(self):
        """刚好等于最小值正常."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 0.0})
        assert cfg.volume == 0.0

    def test_volume_at_max_ok(self):
        """刚好等于最大值正常."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 1.0})
        assert cfg.volume == 1.0

    def test_volume_normal_ok(self):
        """正常范围内音量保持不变."""
        cfg = TtsConfig.parse({"enabled": True, "volume": 0.6})
        assert cfg.volume == 0.6


# ── parse - 枚举值校验测试 ───────────────────────────────────────────────────


class TestTtsConfigParseEnumValues:
    """TtsConfig.parse 枚举值校验测试."""

    # align_mode

    def test_align_mode_subtitle(self):
        """subtitle对齐模式有效."""
        cfg = TtsConfig.parse({"enabled": True, "align_mode": "subtitle"})
        assert cfg.align_mode == "subtitle"

    def test_align_mode_full(self):
        """full对齐模式有效."""
        cfg = TtsConfig.parse({"enabled": True, "align_mode": "full"})
        assert cfg.align_mode == "full"

    def test_align_mode_invalid_fallback(self):
        """无效align_mode回退到full."""
        cfg = TtsConfig.parse({"enabled": True, "align_mode": "word_by_word"})
        assert cfg.align_mode == "full"

    # overlap_mode

    def test_overlap_mode_replace(self):
        """replace叠加模式有效."""
        cfg = TtsConfig.parse({"enabled": True, "overlap_mode": "replace"})
        assert cfg.overlap_mode == "replace"

    def test_overlap_mode_mix(self):
        """mix叠加模式有效."""
        cfg = TtsConfig.parse({"enabled": True, "overlap_mode": "mix"})
        assert cfg.overlap_mode == "mix"

    def test_overlap_mode_invalid_fallback(self):
        """无效overlap_mode回退到replace."""
        cfg = TtsConfig.parse({"enabled": True, "overlap_mode": "duck"})
        assert cfg.overlap_mode == "replace"


# ── _clamp 直接测试 ──────────────────────────────────────────────────────────


class TestTtsConfigClampDirect:
    """_clamp 方法直接调用测试."""

    def test_clamp_speed_low(self):
        """手动构造低语速再clamp."""
        cfg = TtsConfig(enabled=True, speed=0.1)
        cfg._clamp()
        assert cfg.speed == 0.5

    def test_clamp_speed_high(self):
        """手动构造高速再clamp."""
        cfg = TtsConfig(enabled=True, speed=10)
        cfg._clamp()
        assert cfg.speed == 2.0

    def test_clamp_pitch_low(self):
        """手动构造低调再clamp."""
        cfg = TtsConfig(enabled=True, pitch=-20)
        cfg._clamp()
        assert cfg.pitch == -12

    def test_clamp_pitch_high(self):
        """手动构造高调再clamp."""
        cfg = TtsConfig(enabled=True, pitch=20)
        cfg._clamp()
        assert cfg.pitch == 12

    def test_clamp_volume_low(self):
        """手动构造低音量再clamp."""
        cfg = TtsConfig(enabled=True, volume=-1)
        cfg._clamp()
        assert cfg.volume == 0.0

    def test_clamp_volume_high(self):
        """手动构造高音量再clamp."""
        cfg = TtsConfig(enabled=True, volume=2)
        cfg._clamp()
        assert cfg.volume == 1.0

    def test_clamp_preserves_in_range(self):
        """范围内的值不变."""
        cfg = TtsConfig(enabled=True, speed=1.2, pitch=3, volume=0.7)
        cfg._clamp()
        assert cfg.speed == 1.2
        assert cfg.pitch == 3
        assert cfg.volume == 0.7


# ── is_dataclass 验证 ───────────────────────────────────────────────────────


class TestTtsConfigStructure:
    """TtsConfig 结构验证."""

    def test_is_dataclass(self):
        """是dataclass."""
        from dataclasses import is_dataclass

        assert is_dataclass(TtsConfig)

    def test_equality(self):
        """相同配置相等."""
        c1 = TtsConfig(enabled=True, voice_id="v1")
        c2 = TtsConfig(enabled=True, voice_id="v1")
        assert c1 == c2

    def test_inequality(self):
        """不同配置不等."""
        c1 = TtsConfig(enabled=True, voice_id="v1")
        c2 = TtsConfig(enabled=True, voice_id="v2")
        assert c1 != c2
