"""TtsConfig 单元测试."""

from __future__ import annotations

import pytest
from domain.tts_config import TtsConfig


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

    def test_default_values(self):
        config = TtsConfig()
        assert config.enabled is False
        assert config.voice_id == ""
        assert config.speed == 1.0
        assert config.pitch == 0.0
        assert config.volume == 0.8
        assert config.text == ""
        assert config.align_mode == "full"
        assert config.overlap_mode == "replace"

    def test_custom_construction(self):
        config = TtsConfig(
            enabled=True,
            voice_id="voice_001",
            speed=1.5,
            pitch=3.0,
            volume=0.9,
            text="hello",
            align_mode="subtitle",
            overlap_mode="mix",
        )
        assert config.enabled is True
        assert config.voice_id == "voice_001"
        assert config.speed == 1.5
        assert config.pitch == 3.0
        assert config.volume == 0.9
        assert config.text == "hello"
        assert config.align_mode == "subtitle"
        assert config.overlap_mode == "mix"

    def test_slots_no_extra_attrs(self):
        config = TtsConfig()
        with pytest.raises((AttributeError, TypeError)):
            config.new_attr = "value"  # type: ignore[attr-defined]

    def test_equality_same_values(self):
        a = TtsConfig(enabled=True, voice_id="v1")
        b = TtsConfig(enabled=True, voice_id="v1")
        assert a == b

    def test_equality_different_values(self):
        a = TtsConfig(enabled=True)
        b = TtsConfig(enabled=False)
        assert a != b


class TestTtsConfigParseNoneAndEmpty:
    """parse 空输入测试."""

    def test_parse_none(self):
        config = TtsConfig.parse(None)
        assert config == TtsConfig()

    def test_parse_empty_dict(self):
        config = TtsConfig.parse({})
        assert config == TtsConfig()

    def test_parse_non_dict_string(self):
        config = TtsConfig.parse("not a dict")  # type: ignore[arg-type]
        assert config == TtsConfig()

    def test_parse_non_dict_list(self):
        config = TtsConfig.parse([])  # type: ignore[arg-type]
        assert config == TtsConfig()

    def test_parse_non_dict_number(self):
        config = TtsConfig.parse(123)  # type: ignore[arg-type]
        assert config == TtsConfig()


class TestTtsConfigParseDisabled:
    """parse disabled 场景."""

    def test_parse_enabled_false_returns_default(self):
        config = TtsConfig.parse({"enabled": False})
        assert config.enabled is False
        assert config.speed == 1.0
        assert config.voice_id == ""

    def test_parse_enabled_false_ignores_other_fields(self):
        config = TtsConfig.parse(
            {
                "enabled": False,
                "voice_id": "v1",
                "speed": 1.5,
            }
        )
        assert config.enabled is False
        assert config.voice_id == ""
        assert config.speed == 1.0

    def test_parse_enabled_non_bool_falls_to_false(self):
        config = TtsConfig.parse({"enabled": "true"})
        assert config.enabled is False

    def test_parse_enabled_int_falls_to_false(self):
        config = TtsConfig.parse({"enabled": 1})
        assert config.enabled is False


class TestTtsConfigParseNormal:
    """parse 正常数据测试."""

    def test_parse_full_data(self):
        data = {
            "enabled": True,
            "voice_id": "voice_001",
            "speed": 1.5,
            "pitch": 2.5,
            "volume": 0.7,
            "text": "你好世界",
            "align_mode": "subtitle",
            "overlap_mode": "mix",
        }
        config = TtsConfig.parse(data)
        assert config.enabled is True
        assert config.voice_id == "voice_001"
        assert config.speed == 1.5
        assert config.pitch == 2.5
        assert config.volume == 0.7
        assert config.text == "你好世界"
        assert config.align_mode == "subtitle"
        assert config.overlap_mode == "mix"

    def test_parse_int_speed_becomes_float(self):
        config = TtsConfig.parse({"enabled": True, "speed": 2})
        assert isinstance(config.speed, float)
        assert config.speed == 2.0

    def test_parse_int_pitch_becomes_float(self):
        config = TtsConfig.parse({"enabled": True, "pitch": -3})
        assert isinstance(config.pitch, float)
        assert config.pitch == -3.0


class TestTtsConfigParseTypeFallback:
    """parse 类型错误回退测试."""

    def test_parse_voice_id_non_string_fallback(self):
        config = TtsConfig.parse({"enabled": True, "voice_id": 123})
        assert config.voice_id == ""

    def test_parse_speed_non_numeric_fallback(self):
        config = TtsConfig.parse({"enabled": True, "speed": "fast"})
        assert config.speed == 1.0

    def test_parse_pitch_non_numeric_fallback(self):
        config = TtsConfig.parse({"enabled": True, "pitch": "high"})
        assert config.pitch == 0.0

    def test_parse_volume_non_numeric_fallback(self):
        config = TtsConfig.parse({"enabled": True, "volume": "loud"})
        assert config.volume == 0.8

    def test_parse_text_non_string_fallback(self):
        config = TtsConfig.parse({"enabled": True, "text": 456})
        assert config.text == ""

    def test_parse_voice_id_list_fallback(self):
        config = TtsConfig.parse({"enabled": True, "voice_id": ["v1"]})
        assert config.voice_id == ""


class TestTtsConfigParseClamp:
    """parse 边界钳制测试."""

    def test_parse_speed_below_min_clamped(self):
        config = TtsConfig.parse({"enabled": True, "speed": 0.1})
        assert config.speed == 0.5

    def test_parse_speed_above_max_clamped(self):
        config = TtsConfig.parse({"enabled": True, "speed": 3.0})
        assert config.speed == 2.0

    def test_parse_speed_at_min_ok(self):
        config = TtsConfig.parse({"enabled": True, "speed": 0.5})
        assert config.speed == 0.5

    def test_parse_speed_at_max_ok(self):
        config = TtsConfig.parse({"enabled": True, "speed": 2.0})
        assert config.speed == 2.0

    def test_parse_pitch_below_min_clamped(self):
        config = TtsConfig.parse({"enabled": True, "pitch": -20})
        assert config.pitch == -12

    def test_parse_pitch_above_max_clamped(self):
        config = TtsConfig.parse({"enabled": True, "pitch": 20})
        assert config.pitch == 12

    def test_parse_pitch_at_min_ok(self):
        config = TtsConfig.parse({"enabled": True, "pitch": -12})
        assert config.pitch == -12

    def test_parse_pitch_at_max_ok(self):
        config = TtsConfig.parse({"enabled": True, "pitch": 12})
        assert config.pitch == 12

    def test_parse_volume_below_min_clamped(self):
        config = TtsConfig.parse({"enabled": True, "volume": -0.5})
        assert config.volume == 0.0

    def test_parse_volume_above_max_clamped(self):
        config = TtsConfig.parse({"enabled": True, "volume": 1.5})
        assert config.volume == 1.0

    def test_parse_volume_at_min_ok(self):
        config = TtsConfig.parse({"enabled": True, "volume": 0.0})
        assert config.volume == 0.0

    def test_parse_volume_at_max_ok(self):
        config = TtsConfig.parse({"enabled": True, "volume": 1.0})
        assert config.volume == 1.0


class TestTtsConfigParseAlignMode:
    """align_mode 解析测试."""

    def test_parse_align_mode_subtitle(self):
        config = TtsConfig.parse({"enabled": True, "align_mode": "subtitle"})
        assert config.align_mode == "subtitle"

    def test_parse_align_mode_full(self):
        config = TtsConfig.parse({"enabled": True, "align_mode": "full"})
        assert config.align_mode == "full"

    def test_parse_align_mode_invalid_fallback(self):
        config = TtsConfig.parse({"enabled": True, "align_mode": "auto"})
        assert config.align_mode == "full"

    def test_parse_align_mode_empty_fallback(self):
        config = TtsConfig.parse({"enabled": True, "align_mode": ""})
        assert config.align_mode == "full"


class TestTtsConfigParseOverlapMode:
    """overlap_mode 解析测试."""

    def test_parse_overlap_mode_replace(self):
        config = TtsConfig.parse({"enabled": True, "overlap_mode": "replace"})
        assert config.overlap_mode == "replace"

    def test_parse_overlap_mode_mix(self):
        config = TtsConfig.parse({"enabled": True, "overlap_mode": "mix"})
        assert config.overlap_mode == "mix"

    def test_parse_overlap_mode_invalid_fallback(self):
        config = TtsConfig.parse({"enabled": True, "overlap_mode": "add"})
        assert config.overlap_mode == "replace"

    def test_parse_overlap_mode_empty_fallback(self):
        config = TtsConfig.parse({"enabled": True, "overlap_mode": ""})
        assert config.overlap_mode == "replace"


class TestTtsConfigClamp:
    """_clamp 直接调用测试."""

    def test_clamp_speed_low(self):
        config = TtsConfig(enabled=True, speed=0.1)
        config._clamp()
        assert config.speed == 0.5

    def test_clamp_speed_high(self):
        config = TtsConfig(enabled=True, speed=5.0)
        config._clamp()
        assert config.speed == 2.0

    def test_clamp_speed_normal_unchanged(self):
        config = TtsConfig(enabled=True, speed=1.2)
        config._clamp()
        assert config.speed == 1.2

    def test_clamp_pitch_low(self):
        config = TtsConfig(enabled=True, pitch=-20)
        config._clamp()
        assert config.pitch == -12

    def test_clamp_pitch_high(self):
        config = TtsConfig(enabled=True, pitch=20)
        config._clamp()
        assert config.pitch == 12

    def test_clamp_pitch_normal_unchanged(self):
        config = TtsConfig(enabled=True, pitch=5.0)
        config._clamp()
        assert config.pitch == 5.0

    def test_clamp_volume_low(self):
        config = TtsConfig(enabled=True, volume=-1.0)
        config._clamp()
        assert config.volume == 0.0

    def test_clamp_volume_high(self):
        config = TtsConfig(enabled=True, volume=2.0)
        config._clamp()
        assert config.volume == 1.0

    def test_clamp_volume_normal_unchanged(self):
        config = TtsConfig(enabled=True, volume=0.5)
        config._clamp()
        assert config.volume == 0.5
