"""preset_bgm 预设BGM库单测."""

from dataclasses import FrozenInstanceError

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

# ── PresetBGM dataclass ──────────────────────────────────────────────────────


class TestPresetBGM:
    """PresetBGM dataclass"""

    def test_minimal_creation(self):
        b = PresetBGM(id="test_001", name="测试音乐", style="upbeat", duration=60.0)
        assert b.id == "test_001"
        assert b.name == "测试音乐"
        assert b.style == "upbeat"
        assert b.duration == 60.0
        assert b.artist == ""
        assert b.description == ""
        assert b.tags == []
        assert b.audio_url == ""

    def test_full_creation(self):
        b = PresetBGM(
            id="bgm_001",
            name="阳光清晨",
            style="upbeat",
            duration=120.5,
            artist="音乐人A",
            description="轻快明亮的吉他",
            tags=["轻快", "阳光"],
            audio_url="https://cdn.example.com/bgm.mp3",
        )
        assert b.id == "bgm_001"
        assert b.artist == "音乐人A"
        assert b.description == "轻快明亮的吉他"
        assert b.tags == ["轻快", "阳光"]
        assert b.audio_url == "https://cdn.example.com/bgm.mp3"

    def test_frozen_immutable(self):
        b = PresetBGM(id="test", name="Test", style="relax", duration=100.0)
        with pytest.raises(FrozenInstanceError):
            b.name = "NewName"

    def test_equality(self):
        b1 = PresetBGM(id="same", name="同名", style="upbeat", duration=60.0)
        b2 = PresetBGM(id="same", name="同名", style="upbeat", duration=60.0)
        assert b1 == b2

    def test_inequality(self):
        b1 = PresetBGM(id="a", name="A", style="upbeat", duration=60.0)
        b2 = PresetBGM(id="b", name="B", style="relax", duration=90.0)
        assert b1 != b2

    def test_not_hashable_due_to_list_tags(self):
        # 包含 list 字段(tags)的 frozen dataclass 不可哈希
        b = PresetBGM(id="test", name="Test", style="tech", duration=60.0)
        with pytest.raises(TypeError):
            hash(b)


# ── PRESET_BGM_LIBRARY 清单 ──────────────────────────────────────────────────


class TestPresetBGMLibrary:
    """预设BGM库清单"""

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

    def test_all_are_preset_bgm(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert isinstance(bgm, PresetBGM)

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

    def test_all_have_required_fields(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert bgm.id != ""
            assert bgm.name != ""
            assert bgm.style != ""
            assert bgm.duration > 0

    def test_upbeat_style_count(self):
        upbeats = [b for b in PRESET_BGM_LIBRARY if b.style == "upbeat"]
        assert len(upbeats) >= 3

    def test_relax_style_count(self):
        relax = [b for b in PRESET_BGM_LIBRARY if b.style == "relax"]
        assert len(relax) >= 3

    def test_tech_style_count(self):
        tech = [b for b in PRESET_BGM_LIBRARY if b.style == "tech"]
        assert len(tech) >= 2

    def test_commerce_style_count(self):
        commerce = [b for b in PRESET_BGM_LIBRARY if b.style == "commerce"]
        assert len(commerce) >= 2

    def test_sunny_morning_preset(self):
        b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_upbeat_001")
        assert b.name == "阳光清晨"
        assert b.style == "upbeat"
        assert b.duration == 120.0
        assert "吉他" in b.description
        assert "vlog" in b.tags

    def test_quiet_time_preset(self):
        b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_relax_001")
        assert b.name == "静谧时光"
        assert b.style == "relax"
        assert b.duration == 180.0

    def test_future_tech_preset(self):
        b = next(b for b in PRESET_BGM_LIBRARY if b.id == "bgm_tech_001")
        assert b.name == "未来科技"
        assert b.style == "tech"

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

    def test_all_tags_are_lists(self):
        for bgm in PRESET_BGM_LIBRARY:
            assert isinstance(bgm.tags, list)


# ── BGM_STYLES 风格字典 ─────────────────────────────────────────────────────


class TestBGMStyles:
    """BGM_STYLES 风格分类字典"""

    def test_styles_exist(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_style_names_chinese(self):
        assert BGM_STYLES["upbeat"] == "轻快"
        assert BGM_STYLES["relax"] == "治愈"
        assert BGM_STYLES["tech"] == "科技"
        assert BGM_STYLES["commerce"] == "电商"
        assert BGM_STYLES["emotional"] == "情感"
        assert BGM_STYLES["cinematic"] == "电影"

    def test_library_styles_are_defined(self):
        # 库中的所有风格都应该在 BGM_STYLES 中有定义
        styles_in_library = {b.style for b in PRESET_BGM_LIBRARY}
        for style in styles_in_library:
            assert style in BGM_STYLES, f"style {style} not defined in BGM_STYLES"


# ── get_preset_bgm ──────────────────────────────────────────────────────────


class TestGetPresetBGM:
    """get_preset_bgm 函数"""

    def test_get_existing(self):
        b = get_preset_bgm("bgm_upbeat_001")
        assert b is not None
        assert b.id == "bgm_upbeat_001"
        assert b.name == "阳光清晨"

    def test_get_relax(self):
        b = get_preset_bgm("bgm_relax_002")
        assert b is not None
        assert b.style == "relax"

    def test_get_nonexistent_returns_none(self):
        b = get_preset_bgm("nonexistent_id")
        assert b is None

    def test_get_empty_string_returns_none(self):
        b = get_preset_bgm("")
        assert b is None

    def test_returns_same_instance(self):
        b1 = get_preset_bgm("bgm_upbeat_001")
        b2 = get_preset_bgm("bgm_upbeat_001")
        assert b1 is b2


# ── list_preset_bgm_by_style ─────────────────────────────────────────────────


class TestListPresetBGMByStyle:
    """list_preset_bgm_by_style 函数"""

    def test_upbeat_style(self):
        result = list_preset_bgm_by_style("upbeat")
        assert len(result) >= 3
        for b in result:
            assert b.style == "upbeat"

    def test_relax_style(self):
        result = list_preset_bgm_by_style("relax")
        assert len(result) >= 3
        for b in result:
            assert b.style == "relax"

    def test_tech_style(self):
        result = list_preset_bgm_by_style("tech")
        assert len(result) >= 2

    def test_commerce_style(self):
        result = list_preset_bgm_by_style("commerce")
        assert len(result) >= 2

    def test_unknown_style_empty(self):
        result = list_preset_bgm_by_style("nonexistent_style")
        assert len(result) == 0

    def test_empty_string_empty(self):
        result = list_preset_bgm_by_style("")
        assert len(result) == 0

    def test_returns_new_list(self):
        # 修改返回值不应影响原始列表
        result = list_preset_bgm_by_style("upbeat")
        result.clear()
        assert len(list_preset_bgm_by_style("upbeat")) >= 3


# ── search_preset_bgm ────────────────────────────────────────────────────────


class TestSearchPresetBGM:
    """search_preset_bgm 函数"""

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

    def test_search_by_description(self):
        result = search_preset_bgm("钢琴")
        assert len(result) >= 1
        # 应该匹配描述里有钢琴的

    def test_search_by_tag(self):
        result = search_preset_bgm("vlog")
        assert len(result) >= 1
        assert any("vlog" in b.tags for b in result)

    def test_search_tech_keyword(self):
        result = search_preset_bgm("科技")
        assert len(result) >= 2

    def test_search_case_insensitive(self):
        r1 = search_preset_bgm("UPBEAT")
        r2 = search_preset_bgm("upbeat")
        assert len(r1) == len(r2)

    def test_search_no_match(self):
        result = search_preset_bgm("完全不存在的关键词_xyz123")
        assert len(result) == 0

    def test_search_empty_string(self):
        # 空字符串应该匹配所有（因为 "" in any string 是 True）
        result = search_preset_bgm("")
        assert len(result) == len(PRESET_BGM_LIBRARY)

    def test_search_electronic(self):
        result = search_preset_bgm("电子")
        assert len(result) >= 2

    def test_order_preserved(self):
        # 搜索结果应该保持原列表顺序
        result = search_preset_bgm("bgm")
        ids = [b.id for b in result]
        all_ids = [b.id for b in PRESET_BGM_LIBRARY]
        # 验证相对顺序
        pos_in_result = {bgm_id: i for i, bgm_id in enumerate(ids)}
        prev_pos = -1
        for bgm_id in all_ids:
            if bgm_id in pos_in_result:
                assert pos_in_result[bgm_id] > prev_pos
                prev_pos = pos_in_result[bgm_id]

    def test_search_partial_tag_match(self):
        # 关键词是标签的子串也能匹配
        result = search_preset_bgm("吉他")
        assert len(result) >= 1
