"""preset_voices 预置音色配置单测."""

from dataclasses import FrozenInstanceError

import pytest
from domain.preset_voices import (
    PRESET_VOICES,
    PresetVoice,
    get_preset_voice_by_id,
    get_preset_voices,
    is_preset_voice,
)

# ── PresetVoice dataclass ────────────────────────────────────────────────────


class TestPresetVoice:
    """PresetVoice dataclass"""

    def test_minimal_creation(self):
        v = PresetVoice(
            voice_id="test_v1",
            name="测试音色",
            description="测试描述",
            gender="female",
        )
        assert v.voice_id == "test_v1"
        assert v.name == "测试音色"
        assert v.description == "测试描述"
        assert v.gender == "female"
        assert v.language == "zh-CN"
        assert v.preview_url == ""
        assert v.tags is None

    def test_full_creation(self):
        v = PresetVoice(
            voice_id="test_v2",
            name="完整音色",
            description="完整描述",
            gender="male",
            language="en-US",
            preview_url="https://example.com/preview.mp3",
            tags=["沉稳", "男声"],
        )
        assert v.gender == "male"
        assert v.language == "en-US"
        assert v.preview_url == "https://example.com/preview.mp3"
        assert v.tags == ["沉稳", "男声"]

    def test_frozen_immutable(self):
        v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
        with pytest.raises(FrozenInstanceError):
            v.name = "NewName"

    def test_equality(self):
        v1 = PresetVoice(voice_id="same", name="同名", description="d", gender="female")
        v2 = PresetVoice(voice_id="same", name="同名", description="d", gender="female")
        assert v1 == v2

    def test_inequality(self):
        v1 = PresetVoice(voice_id="a", name="A", description="da", gender="female")
        v2 = PresetVoice(voice_id="b", name="B", description="db", gender="male")
        assert v1 != v2

    def test_to_dict(self):
        v = PresetVoice(
            voice_id="test_v1",
            name="测试音色",
            description="测试描述",
            gender="female",
            language="zh-CN",
            preview_url="https://x.com/a.mp3",
            tags=["温柔", "女声"],
        )
        d = v.to_dict()
        assert isinstance(d, dict)
        assert d["voice_id"] == "test_v1"
        assert d["name"] == "测试音色"
        assert d["description"] == "测试描述"
        assert d["gender"] == "female"
        assert d["language"] == "zh-CN"
        assert d["preview_url"] == "https://x.com/a.mp3"
        assert d["tags"] == ["温柔", "女声"]

    def test_to_dict_none_tags_becomes_empty_list(self):
        v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
        d = v.to_dict()
        assert d["tags"] == []
        assert isinstance(d["tags"], list)

    def test_to_dict_has_all_keys(self):
        v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
        d = v.to_dict()
        assert set(d.keys()) == {
            "voice_id",
            "name",
            "description",
            "gender",
            "language",
            "preview_url",
            "tags",
        }

    def test_slots_no_extra_attrs(self):
        v = PresetVoice(voice_id="test", name="Test", description="desc", gender="female")
        # frozen + slots dataclass 不允许动态添加属性
        with pytest.raises((AttributeError, TypeError)):
            v.nonexistent_field = "value"


# ── PRESET_VOICES 列表 ───────────────────────────────────────────────────────


class TestPresetVoicesList:
    """PRESET_VOICES 预置音色列表"""

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

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

    def test_all_are_preset_voice(self):
        for v in PRESET_VOICES:
            assert isinstance(v, PresetVoice)

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

    def test_unique_names(self):
        names = [v.name for v in PRESET_VOICES]
        assert len(names) == len(set(names))

    def test_all_have_required_fields(self):
        for v in PRESET_VOICES:
            assert v.voice_id != ""
            assert v.name != ""
            assert v.description != ""
            assert v.gender in ("male", "female")

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

    def test_longxiaochun_voice(self):
        v = next(v for v in PRESET_VOICES if v.voice_id == "longxiaochun_v3")
        assert v.name == "龙小淳"
        assert v.gender == "female"
        assert "知性" in v.description

    def test_longsanshu_voice(self):
        v = next(v for v in PRESET_VOICES if v.voice_id == "longsanshu_v3")
        assert v.name == "龙三叔"
        assert v.gender == "male"

    def test_male_voices_count(self):
        males = [v for v in PRESET_VOICES if v.gender == "male"]
        assert len(males) == 3  # 龙三叔/龙书/龙硕

    def test_female_voices_count(self):
        females = [v for v in PRESET_VOICES if v.gender == "female"]
        assert len(females) == 5  # 龙小淳/龙小夏/龙悦/龙应静/龙甜

    def test_all_have_tags(self):
        for v in PRESET_VOICES:
            assert v.tags is not None
            assert len(v.tags) > 0

    def test_voice_id_pattern(self):
        # 所有音色 ID 都以 _v3 结尾
        for v in PRESET_VOICES:
            assert v.voice_id.endswith("_v3")


# ── 工具函数 ─────────────────────────────────────────────────────────────────


class TestGetPresetVoices:
    """get_preset_voices 函数"""

    def test_returns_full_list(self):
        result = get_preset_voices()
        assert len(result) == len(PRESET_VOICES)
        assert result is PRESET_VOICES  # 返回同一列表引用

    def test_all_are_preset_voice(self):
        result = get_preset_voices()
        for v in result:
            assert isinstance(v, PresetVoice)


class TestGetPresetVoiceById:
    """get_preset_voice_by_id 函数"""

    def test_get_existing_female(self):
        v = get_preset_voice_by_id("longxiaochun_v3")
        assert v is not None
        assert v.voice_id == "longxiaochun_v3"
        assert v.name == "龙小淳"

    def test_get_existing_male(self):
        v = get_preset_voice_by_id("longsanshu_v3")
        assert v is not None
        assert v.gender == "male"

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

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

    def test_returns_same_instance(self):
        v1 = get_preset_voice_by_id("longyue_v3")
        v2 = get_preset_voice_by_id("longyue_v3")
        assert v1 is v2

    def test_all_voices_reachable(self):
        for v in PRESET_VOICES:
            found = get_preset_voice_by_id(v.voice_id)
            assert found is not None
            assert found.voice_id == v.voice_id


class TestIsPresetVoice:
    """is_preset_voice 函数"""

    def test_existing_voice_true(self):
        assert is_preset_voice("longxiaochun_v3") is True

    def test_all_existing_are_true(self):
        for v in PRESET_VOICES:
            assert is_preset_voice(v.voice_id) is True

    def test_nonexistent_voice_false(self):
        assert is_preset_voice("fake_voice") is False

    def test_empty_string_false(self):
        assert is_preset_voice("") is False

    def test_consistent_with_get_by_id(self):
        for v in PRESET_VOICES:
            assert is_preset_voice(v.voice_id) == (get_preset_voice_by_id(v.voice_id) is not None)
