"""voice_presets 配音音色预设模块单测."""

import pytest
from domain.voice_presets import (
    MOCK_VOICES,
    VoiceGender,
    VoicePreset,
    VoiceStyle,
    get_default_voice,
    get_voice,
    list_voices,
)

# ── 枚举测试 ──────────────────────────────────────────────────────────────────


class TestVoiceGender:
    """VoiceGender 音色性别枚举"""

    def test_enum_values(self):
        assert VoiceGender.MALE.value == "male"
        assert VoiceGender.FEMALE.value == "female"
        assert VoiceGender.CHILD.value == "child"

    def test_is_str(self):
        assert isinstance(VoiceGender.MALE, str)
        assert VoiceGender.FEMALE == "female"

    def test_from_string(self):
        assert VoiceGender("male") == VoiceGender.MALE
        assert VoiceGender("child") == VoiceGender.CHILD

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            VoiceGender("unknown")


class TestVoiceStyle:
    """VoiceStyle 音色风格枚举"""

    def test_enum_values(self):
        assert VoiceStyle.STABLE.value == "stable"
        assert VoiceStyle.LIVELY.value == "lively"
        assert VoiceStyle.CUSTOMER_SERVICE.value == "customer_service"
        assert VoiceStyle.NARRATION.value == "narration"
        assert VoiceStyle.NEWS.value == "news"
        assert VoiceStyle.STORY.value == "story"

    def test_is_str(self):
        assert isinstance(VoiceStyle.NARRATION, str)
        assert VoiceStyle.STORY == "story"

    def test_from_string(self):
        assert VoiceStyle("news") == VoiceStyle.NEWS

    def test_invalid_raises(self):
        with pytest.raises(ValueError):
            VoiceStyle("rock")


# ── VoicePreset dataclass ─────────────────────────────────────────────────────


class TestVoicePreset:
    """VoicePreset 音色预设 dataclass"""

    def test_minimal_creation(self):
        v = VoicePreset(voice_id="test_voice", name="测试音色")
        assert v.voice_id == "test_voice"
        assert v.name == "测试音色"
        # 默认值
        assert v.gender == VoiceGender.FEMALE
        assert v.style == VoiceStyle.NARRATION
        assert v.description == ""
        assert v.provider == "mock"
        assert v.provider_voice_id == ""
        assert v.default_speed == 1.0
        assert v.default_pitch == 0.0
        assert v.sample_rate == 22050
        assert v.language == "zh-CN"

    def test_full_creation(self):
        v = VoicePreset(
            voice_id="male_deep",
            name="深沉男声",
            gender=VoiceGender.MALE,
            style=VoiceStyle.STABLE,
            description="非常深沉的男声",
            provider="aliyun",
            provider_voice_id="zhiyuan",
            default_speed=0.8,
            default_pitch=-1.0,
            sample_rate=16000,
            language="zh-CN",
        )
        assert v.voice_id == "male_deep"
        assert v.gender == VoiceGender.MALE
        assert v.style == VoiceStyle.STABLE
        assert v.provider == "aliyun"
        assert v.default_speed == 0.8
        assert v.sample_rate == 16000

    def test_str_gender_creation(self):
        # 用字符串值创建也可以（因为是 StrEnum）
        v = VoicePreset(voice_id="v1", name="V1", gender="male")
        assert v.gender == VoiceGender.MALE

    def test_str_style_creation(self):
        v = VoicePreset(voice_id="v1", name="V1", style="news")
        assert v.style == VoiceStyle.NEWS

    def test_equality(self):
        v1 = VoicePreset(voice_id="same", name="同名")
        v2 = VoicePreset(voice_id="same", name="同名")
        assert v1 == v2

    def test_inequality(self):
        v1 = VoicePreset(voice_id="a", name="A")
        v2 = VoicePreset(voice_id="b", name="B")
        assert v1 != v2

    def test_slots_no_extra_attrs(self):
        v = VoicePreset(voice_id="test", name="Test")
        with pytest.raises(AttributeError):
            v.nonexistent_field = "value"


# ── MOCK_VOICES 列表 ─────────────────────────────────────────────────────────


class TestMockVoices:
    """Mock 音色预设列表"""

    def test_not_empty(self):
        assert len(MOCK_VOICES) > 0

    def test_count(self):
        assert len(MOCK_VOICES) == 8

    def test_all_are_voice_preset(self):
        for v in MOCK_VOICES:
            assert isinstance(v, VoicePreset)

    def test_unique_voice_ids(self):
        ids = [v.voice_id for v in MOCK_VOICES]
        assert len(ids) == len(set(ids))

    def test_female_warm_preset(self):
        v = next(v for v in MOCK_VOICES if v.voice_id == "female_warm")
        assert v.name == "温暖女声"
        assert v.gender == VoiceGender.FEMALE
        assert v.style == VoiceStyle.NARRATION
        assert v.default_speed == 1.0
        assert "温柔" in v.description

    def test_male_stable_preset(self):
        v = next(v for v in MOCK_VOICES if v.voice_id == "male_stable")
        assert v.name == "沉稳男声"
        assert v.gender == VoiceGender.MALE
        assert v.style == VoiceStyle.STABLE
        assert v.default_speed == 0.9

    def test_female_lively_preset(self):
        v = next(v for v in MOCK_VOICES if v.voice_id == "female_lively")
        assert v.gender == VoiceGender.FEMALE
        assert v.style == VoiceStyle.LIVELY
        assert v.default_speed == 1.2
        assert v.default_pitch == 2.0

    def test_child_cute_preset(self):
        v = next(v for v in MOCK_VOICES if v.voice_id == "child_cute")
        assert v.gender == VoiceGender.CHILD
        assert v.style == VoiceStyle.STORY
        assert v.default_pitch == 4.0

    def test_all_mock_provider(self):
        for v in MOCK_VOICES:
            assert v.provider == "mock"

    def test_all_have_provider_voice_id(self):
        for v in MOCK_VOICES:
            assert v.provider_voice_id != ""

    def test_all_chinese(self):
        for v in MOCK_VOICES:
            assert v.language == "zh-CN"


# ── get_voice ─────────────────────────────────────────────────────────────────


class TestGetVoice:
    """get_voice 函数"""

    def test_get_existing_voice(self):
        v = get_voice("female_warm")
        assert v is not None
        assert v.voice_id == "female_warm"
        assert v.name == "温暖女声"

    def test_get_male_stable(self):
        v = get_voice("male_stable")
        assert v is not None
        assert v.gender == VoiceGender.MALE

    def test_get_child_cute(self):
        v = get_voice("child_cute")
        assert v is not None
        assert v.gender == VoiceGender.CHILD

    def test_get_nonexistent_returns_none(self):
        v = get_voice("nonexistent_voice")
        assert v is None

    def test_get_empty_string_returns_none(self):
        v = get_voice("")
        assert v is None

    def test_non_mock_provider_returns_none(self):
        v = get_voice("female_warm", provider="aliyun")
        assert v is None

    def test_non_mock_provider_nonexistent(self):
        v = get_voice("whatever", provider="xunfei")
        assert v is None

    def test_mock_provider_explicit(self):
        v = get_voice("female_warm", provider="mock")
        assert v is not None
        assert v.voice_id == "female_warm"

    def test_returns_same_instance(self):
        # 应该返回同一个对象（缓存的）
        v1 = get_voice("female_warm")
        v2 = get_voice("female_warm")
        assert v1 is v2


# ── list_voices ───────────────────────────────────────────────────────────────


class TestListVoices:
    """list_voices 函数"""

    def test_no_filters_returns_all(self):
        result = list_voices()
        assert len(result) == len(MOCK_VOICES)
        assert len(result) == 8

    def test_filter_by_gender_male(self):
        result = list_voices(gender="male")
        assert len(result) > 0
        for v in result:
            assert v.gender == VoiceGender.MALE

    def test_filter_by_gender_female(self):
        result = list_voices(gender="female")
        assert len(result) > 0
        for v in result:
            assert v.gender == VoiceGender.FEMALE

    def test_filter_by_gender_child(self):
        result = list_voices(gender="child")
        assert len(result) == 1
        assert result[0].voice_id == "child_cute"

    def test_filter_by_gender_invalid_returns_empty(self):
        result = list_voices(gender="alien")
        assert len(result) == 0

    def test_filter_by_style_stable(self):
        result = list_voices(style="stable")
        assert len(result) > 0
        for v in result:
            assert v.style == VoiceStyle.STABLE

    def test_filter_by_style_lively(self):
        result = list_voices(style="lively")
        assert len(result) == 1
        assert result[0].voice_id == "female_lively"

    def test_filter_by_style_story(self):
        result = list_voices(style="story")
        assert len(result) >= 2
        for v in result:
            assert v.style == VoiceStyle.STORY

    def test_filter_by_style_invalid_returns_empty(self):
        result = list_voices(style="punk")
        assert len(result) == 0

    def test_filter_by_provider_mock(self):
        result = list_voices(provider="mock")
        assert len(result) == len(MOCK_VOICES)

    def test_filter_by_provider_other_returns_empty(self):
        result = list_voices(provider="aliyun")
        assert len(result) == 0

    def test_filter_by_keyword_name(self):
        result = list_voices(keyword="女声")
        assert len(result) > 0
        for v in result:
            assert "女声" in v.name or "女声" in v.description or "女声" in v.voice_id

    def test_filter_by_keyword_description(self):
        result = list_voices(keyword="商务")
        assert len(result) > 0
        # 沉稳男声描述里有"商务"

    def test_filter_by_keyword_voice_id(self):
        result = list_voices(keyword="male_stable")
        assert len(result) == 1
        assert result[0].voice_id == "male_stable"

    def test_filter_by_keyword_case_insensitive(self):
        result1 = list_voices(keyword="Female")
        result2 = list_voices(keyword="female")
        assert len(result1) == len(result2)

    def test_filter_by_keyword_nonexistent(self):
        result = list_voices(keyword="不存在的关键词999")
        assert len(result) == 0

    def test_combined_gender_and_style(self):
        result = list_voices(gender="female", style="lively")
        assert len(result) == 1
        assert result[0].voice_id == "female_lively"

    def test_combined_gender_style_keyword(self):
        result = list_voices(gender="male", style="story", keyword="磁性")
        assert len(result) == 1
        assert result[0].voice_id == "male_magnetic"

    def test_combined_no_match(self):
        result = list_voices(gender="child", style="news")
        assert len(result) == 0

    def test_returns_new_list(self):
        # 修改返回值不应影响原始列表
        result = list_voices()
        result.clear()
        assert len(MOCK_VOICES) == 8


# ── get_default_voice ─────────────────────────────────────────────────────────


class TestGetDefaultVoice:
    """get_default_voice 函数"""

    def test_returns_voice_preset(self):
        v = get_default_voice()
        assert isinstance(v, VoicePreset)

    def test_returns_first_mock_voice(self):
        v = get_default_voice()
        assert v == MOCK_VOICES[0]

    def test_default_is_female_warm(self):
        v = get_default_voice()
        assert v.voice_id == "female_warm"
        assert v.gender == VoiceGender.FEMALE

    def test_multiple_calls_same(self):
        v1 = get_default_voice()
        v2 = get_default_voice()
        assert v1 is v2
