"""Preset BGM 预设背景音乐单元测试。"""

from dataclasses import FrozenInstanceError

import pytest

from packages.domain.preset_bgm import (
    BGM_STYLES,
    PRESET_BGM_LIBRARY,
    PresetBGM,
    get_preset_bgm,
    list_preset_bgm_by_style,
    search_preset_bgm,
)


class TestPresetBGMDataclass:
    def test_creation_required_fields(self):
        bgm = PresetBGM(id="test_001", name="Test BGM", style="upbeat", duration=120.0)
        assert bgm.id == "test_001"
        assert bgm.name == "Test BGM"
        assert bgm.style == "upbeat"
        assert bgm.duration == 120.0
        assert bgm.artist == ""
        assert bgm.description == ""
        assert bgm.tags == []
        assert bgm.audio_url == ""

    def test_creation_all_fields(self):
        bgm = PresetBGM(
            id="test_002",
            name="Full BGM",
            style="relax",
            duration=180.5,
            artist="Artist Name",
            description="A test description",
            tags=["tag1", "tag2"],
            audio_url="https://cdn/test.mp3",
        )
        assert bgm.artist == "Artist Name"
        assert bgm.description == "A test description"
        assert bgm.tags == ["tag1", "tag2"]
        assert bgm.audio_url == "https://cdn/test.mp3"

    def test_frozen_immutable(self):
        bgm = PresetBGM(id="t1", name="T", style="upbeat", duration=60.0)
        with pytest.raises(FrozenInstanceError):
            bgm.name = "new name"

    def test_equality(self):
        bgm1 = PresetBGM(id="same", name="N", style="upbeat", duration=60.0)
        bgm2 = PresetBGM(id="same", name="N", style="upbeat", duration=60.0)
        assert bgm1 == bgm2

    def test_inequality(self):
        bgm1 = PresetBGM(id="a", name="A", style="upbeat", duration=60.0)
        bgm2 = PresetBGM(id="b", name="B", style="upbeat", duration=60.0)
        assert bgm1 != bgm2

    def test_frozen_with_list_field_not_hashable(self):
        # 包含 list 字段的 frozen dataclass 仍然不可哈希（list 不可哈希）
        bgm = PresetBGM(id="h1", name="H", style="upbeat", duration=60.0, tags=["a"])
        with pytest.raises(TypeError, match="unhashable"):
            hash(bgm)


class TestPresetBGMLibrary:
    def test_library_not_empty(self):
        assert len(PRESET_BGM_LIBRARY) > 0

    def test_library_has_entries(self):
        assert len(PRESET_BGM_LIBRARY) >= 10

    def test_all_have_unique_ids(self):
        ids = [bgm.id for bgm in PRESET_BGM_LIBRARY]
        assert len(ids) == len(set(ids))

    def test_all_have_valid_styles(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert bgm.style in BGM_STYLES

    def test_all_have_positive_duration(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert bgm.duration > 0

    def test_all_have_non_empty_name(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert bgm.name.strip() != ""


class TestBGMStyles:
    def test_styles_dict_keys(self):
        assert "upbeat" in BGM_STYLES
        assert "relax" in BGM_STYLES
        assert "tech" in BGM_STYLES
        assert "commerce" in BGM_STYLES
        assert "emotional" in BGM_STYLES
        assert "cinematic" in BGM_STYLES

    def test_styles_have_chinese_names(self):
        for _, value in BGM_STYLES.items():
            assert isinstance(value, str)
            assert len(value) > 0


class TestGetPresetBGM:
    def test_get_existing(self):
        bgm = get_preset_bgm("bgm_upbeat_001")
        assert bgm is not None
        assert bgm.id == "bgm_upbeat_001"
        assert bgm.name == "阳光清晨"
        assert bgm.style == "upbeat"

    def test_get_nonexistent(self):
        assert get_preset_bgm("nonexistent_id") is None

    def test_get_empty_string(self):
        assert get_preset_bgm("") is None

    def test_get_returns_same_object(self):
        bgm1 = get_preset_bgm("bgm_relax_001")
        bgm2 = get_preset_bgm("bgm_relax_001")
        assert bgm1 is bgm2  # 同一实例（引用同一列表中的对象）


class TestListPresetBGMByStyle:
    def test_list_upbeat(self):
        results = list_preset_bgm_by_style("upbeat")
        assert len(results) >= 3
        for bgm in results:
            assert bgm.style == "upbeat"

    def test_list_relax(self):
        results = list_preset_bgm_by_style("relax")
        assert len(results) >= 3
        for bgm in results:
            assert bgm.style == "relax"

    def test_list_tech(self):
        results = list_preset_bgm_by_style("tech")
        assert len(results) >= 2
        for bgm in results:
            assert bgm.style == "tech"

    def test_list_commerce(self):
        results = list_preset_bgm_by_style("commerce")
        assert len(results) >= 2
        for bgm in results:
            assert bgm.style == "commerce"

    def test_list_empty_style(self):
        results = list_preset_bgm_by_style("nonexistent_style")
        assert results == []

    def test_list_preserves_order(self):
        results = list_preset_bgm_by_style("upbeat")
        ids = [b.id for b in results]
        # 应该按照在列表中的出现顺序排列
        assert ids == sorted(ids, key=lambda x: PRESET_BGM_LIBRARY.index(get_preset_bgm(x)))


class TestSearchPresetBGM:
    def test_search_by_name(self):
        results = search_preset_bgm("阳光")
        assert len(results) >= 1
        assert any("阳光" in b.name for b in results)

    def test_search_by_description(self):
        results = search_preset_bgm("钢琴")
        assert len(results) >= 1
        # 钢琴出现在名称或描述或标签中
        found = False
        for b in results:
            if "钢琴" in b.description or "钢琴" in b.name or "钢琴" in b.tags:
                found = True
                break
        assert found

    def test_search_by_tag(self):
        results = search_preset_bgm("科技")
        assert len(results) >= 1
        found_tech = any(b.style == "tech" for b in results)
        assert found_tech

    def test_search_case_insensitive(self):
        results1 = search_preset_bgm("Tech")
        results2 = search_preset_bgm("tech")
        assert len(results1) == len(results2)

    def test_search_no_match(self):
        results = search_preset_bgm("zzzzzzzzzzz_nonexistent_keyword")
        assert results == []

    def test_search_empty_keyword(self):
        # 空字符串应该匹配所有（因为空字符串 in 任何字符串都是 True）
        results = search_preset_bgm("")
        assert len(results) == len(PRESET_BGM_LIBRARY)

    def test_search_no_duplicates(self):
        # 确保同一个 BGM 不会出现多次
        results = search_preset_bgm("电子")
        ids = [b.id for b in results]
        assert len(ids) == len(set(ids))
