"""intro_outro_config 片头片尾配置单测."""

import pytest
from domain.intro_outro_config import (
    INTRO_OUTRO_TYPE_FOLLOW,
    INTRO_OUTRO_TYPE_NONE,
    INTRO_OUTRO_TYPE_TEXT,
    INTRO_OUTRO_TYPE_VIDEO,
    TRANSITION_FADE,
    TRANSITION_SLIDE,
    TRANSITION_WIPE,
    IntroOutroConfig,
)

# ── 常量测试 ──────────────────────────────────────────────────────────────────


class TestConstants:
    """模块常量"""

    def test_type_constants(self):
        assert INTRO_OUTRO_TYPE_NONE == "none"
        assert INTRO_OUTRO_TYPE_VIDEO == "video"
        assert INTRO_OUTRO_TYPE_TEXT == "text"
        assert INTRO_OUTRO_TYPE_FOLLOW == "follow"

    def test_transition_constants(self):
        assert TRANSITION_FADE == "fade"
        assert TRANSITION_SLIDE == "slide"
        assert TRANSITION_WIPE == "wipe"


# ── 默认值与基础属性 ──────────────────────────────────────────────────────────


class TestDefaultConfig:
    """IntroOutroConfig 默认值"""

    def test_default_not_enabled(self):
        c = IntroOutroConfig()
        assert c.enabled is False

    def test_default_intro(self):
        c = IntroOutroConfig()
        assert c.intro_type == INTRO_OUTRO_TYPE_NONE
        assert c.intro_video_path == ""
        assert c.intro_duration == 3.0
        assert c.intro_background == "#000000"
        assert c.intro_title == ""
        assert c.intro_subtitle == ""
        assert c.intro_title_color == "white"
        assert c.intro_title_size == 48
        assert c.intro_subtitle_color == "gray"
        assert c.intro_subtitle_size == 24

    def test_default_outro(self):
        c = IntroOutroConfig()
        assert c.outro_type == INTRO_OUTRO_TYPE_NONE
        assert c.outro_video_path == ""
        assert c.outro_duration == 3.0
        assert c.outro_background == "#000000"
        assert c.outro_title == "感谢观看"
        assert c.outro_subtitle == "点赞关注不迷路"
        assert c.outro_title_color == "white"
        assert c.outro_title_size == 48
        assert c.outro_subtitle_color == "gray"
        assert c.outro_subtitle_size == 24

    def test_default_transition(self):
        c = IntroOutroConfig()
        assert c.transition_effect == TRANSITION_FADE
        assert c.transition_duration == 0.5


# ── from_dict 构造 ───────────────────────────────────────────────────────────


class TestFromDict:
    """from_dict 工厂方法"""

    def test_none_returns_default(self):
        c = IntroOutroConfig.from_dict(None)
        assert c.enabled is False

    def test_empty_dict_returns_default(self):
        c = IntroOutroConfig.from_dict({})
        assert c.enabled is False

    def test_enabled_false_returns_default(self):
        c = IntroOutroConfig.from_dict({"enabled": False})
        assert c.enabled is False

    def test_minimal_enabled(self):
        c = IntroOutroConfig.from_dict({"enabled": True})
        assert c.enabled is True
        assert c.intro_type == INTRO_OUTRO_TYPE_NONE
        assert c.outro_type == INTRO_OUTRO_TYPE_NONE

    def test_intro_video(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {
                    "type": "video",
                    "video_path": "/tmp/intro.mp4",
                    "duration": 5.0,
                },
            }
        )
        assert c.intro_type == INTRO_OUTRO_TYPE_VIDEO
        assert c.intro_video_path == "/tmp/intro.mp4"
        assert c.intro_duration == 5.0

    def test_intro_text(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {
                    "type": "text",
                    "title": "欢迎来到",
                    "subtitle": "我的频道",
                    "background": "#ffffff",
                    "title_color": "black",
                    "title_size": 64,
                    "subtitle_color": "darkgray",
                    "subtitle_size": 32,
                },
            }
        )
        assert c.intro_type == INTRO_OUTRO_TYPE_TEXT
        assert c.intro_title == "欢迎来到"
        assert c.intro_subtitle == "我的频道"
        assert c.intro_background == "#ffffff"
        assert c.intro_title_size == 64
        assert c.intro_subtitle_size == 32

    def test_outro_text(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {
                    "type": "text",
                    "title": "再见",
                    "subtitle": "下次见",
                    "title_size": 56,
                },
            }
        )
        assert c.outro_type == INTRO_OUTRO_TYPE_TEXT
        assert c.outro_title == "再见"
        assert c.outro_subtitle == "下次见"
        assert c.outro_title_size == 56

    def test_outro_follow_type(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"type": "follow", "title": "关注我"},
            }
        )
        assert c.outro_type == INTRO_OUTRO_TYPE_FOLLOW
        assert c.outro_title == "关注我"

    def test_outro_video(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {
                    "type": "video",
                    "video_path": "/tmp/outro.mp4",
                    "duration": 4.0,
                },
            }
        )
        assert c.outro_type == INTRO_OUTRO_TYPE_VIDEO
        assert c.outro_video_path == "/tmp/outro.mp4"
        assert c.outro_duration == 4.0

    def test_transition_config(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "transition": "slide",
                "transition_duration": 1.0,
            }
        )
        assert c.transition_effect == TRANSITION_SLIDE
        assert c.transition_duration == 1.0

    def test_video_field_alias(self):
        # video 字段兼容（video_path 和 video 都能用）
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"type": "video", "video": "old_path.mp4"},
            }
        )
        assert c.intro_video_path == "old_path.mp4"

    def test_video_path_preferred_over_video(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"type": "video", "video_path": "new.mp4", "video": "old.mp4"},
            }
        )
        assert c.intro_video_path == "new.mp4"

    def test_invalid_duration_falls_back(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"duration": "abc"},
            }
        )
        assert c.intro_duration == 3.0

    def test_invalid_size_falls_back(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"title_size": "not_a_number"},
            }
        )
        assert c.intro_title_size == 48

    def test_none_intro_outro(self):
        c = IntroOutroConfig.from_dict({"enabled": True, "intro": None, "outro": None})
        assert c.intro_type == INTRO_OUTRO_TYPE_NONE
        assert c.outro_type == INTRO_OUTRO_TYPE_NONE

    def test_empty_title_defaults_for_outro(self):
        # outro title 为空时回退到默认值
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"type": "text", "title": ""},
            }
        )
        assert c.outro_title == "感谢观看"

    def test_empty_subtitle_defaults_for_outro(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"subtitle": ""},
            }
        )
        assert c.outro_subtitle == "点赞关注不迷路"

    def test_combined_full_config(self):
        c = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {
                    "type": "text",
                    "title": "片头标题",
                    "subtitle": "片头副标题",
                    "background": "#123456",
                    "duration": 2.5,
                    "title_size": 72,
                },
                "outro": {
                    "type": "video",
                    "video_path": "/outro.mp4",
                    "duration": 4.0,
                },
                "transition": "wipe",
                "transition_duration": 0.8,
            }
        )
        assert c.intro_title == "片头标题"
        assert c.intro_duration == 2.5
        assert c.outro_type == "video"
        assert c.outro_video_path == "/outro.mp4"
        assert c.transition_effect == "wipe"
        assert c.transition_duration == 0.8

    def test_does_not_mutate_input(self):
        data = {"enabled": True, "intro": {"type": "text", "title": "test"}}
        data_copy = {
            "enabled": True,
            "intro": {"type": "text", "title": "test"},
        }
        IntroOutroConfig.from_dict(data)
        assert data == data_copy


# ── has_intro / has_outro 属性 ───────────────────────────────────────────────


class TestHasIntroOutro:
    """has_intro / has_outro 属性"""

    def test_disabled_no_intro(self):
        c = IntroOutroConfig()
        assert c.has_intro is False

    def test_disabled_no_outro(self):
        c = IntroOutroConfig()
        assert c.has_outro is False

    def test_enabled_none_type_no_intro(self):
        c = IntroOutroConfig(enabled=True, intro_type=INTRO_OUTRO_TYPE_NONE)
        assert c.has_intro is False

    def test_video_intro_has_intro(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_VIDEO,
            intro_video_path="/x.mp4",
        )
        assert c.has_intro is True

    def test_text_intro_has_intro(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
        )
        assert c.has_intro is True

    def test_video_outro_has_outro(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_VIDEO,
            outro_video_path="/x.mp4",
        )
        assert c.has_outro is True

    def test_text_outro_has_outro(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
        )
        assert c.has_outro is True

    def test_follow_outro_has_outro(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_FOLLOW,
            outro_title="关注",
        )
        assert c.has_outro is True

    def test_follow_type_no_intro(self):
        # follow 只是片尾类型，片头不支持
        c = IntroOutroConfig(enabled=True, intro_type=INTRO_OUTRO_TYPE_FOLLOW)
        assert c.has_intro is False


# ── total_extra_duration ─────────────────────────────────────────────────────


class TestTotalExtraDuration:
    """total_extra_duration 属性"""

    def test_disabled_zero(self):
        c = IntroOutroConfig()
        assert c.total_extra_duration == 0.0

    def test_only_intro(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_duration=3.0,
        )
        assert c.total_extra_duration == 3.0

    def test_only_outro(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
            outro_duration=4.0,
        )
        assert c.total_extra_duration == 4.0

    def test_both_intro_outro(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_VIDEO,
            intro_video_path="/i.mp4",
            intro_duration=2.5,
            outro_type=INTRO_OUTRO_TYPE_VIDEO,
            outro_video_path="/o.mp4",
            outro_duration=3.5,
        )
        assert c.total_extra_duration == 6.0

    def test_zero_duration_not_counted(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_duration=0.0,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
            outro_duration=0.0,
        )
        assert c.total_extra_duration == 0.0

    def test_negative_duration_not_counted(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_duration=-1.0,
        )
        assert c.total_extra_duration == 0.0


# ── validate 校验 ────────────────────────────────────────────────────────────


class TestValidate:
    """validate 方法"""

    def test_disabled_always_valid(self):
        c = IntroOutroConfig()
        valid, msg = c.validate()
        assert valid is True
        assert msg == ""

    def test_none_types_valid(self):
        c = IntroOutroConfig(enabled=True)
        valid, msg = c.validate()
        assert valid is True

    def test_invalid_intro_type(self):
        c = IntroOutroConfig(enabled=True, intro_type="invalid")
        valid, msg = c.validate()
        assert valid is False
        assert "片头类型" in msg

    def test_invalid_outro_type(self):
        c = IntroOutroConfig(enabled=True, outro_type="invalid")
        valid, msg = c.validate()
        assert valid is False
        assert "片尾类型" in msg

    def test_video_intro_no_path(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_VIDEO,
            intro_video_path="",
        )
        valid, msg = c.validate()
        assert valid is False
        assert "video_path" in msg

    def test_video_intro_with_path_valid(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_VIDEO,
            intro_video_path="/path.mp4",
        )
        valid, _ = c.validate()
        assert valid is True

    def test_text_intro_no_title(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="",
        )
        valid, msg = c.validate()
        assert valid is False
        assert "title" in msg

    def test_text_intro_with_title_valid(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
        )
        valid, _ = c.validate()
        assert valid is True

    def test_video_outro_no_path(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_VIDEO,
            outro_video_path="",
        )
        valid, msg = c.validate()
        assert valid is False
        assert "video_path" in msg

    def test_text_outro_no_title(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="",
        )
        valid, msg = c.validate()
        assert valid is False
        assert "title" in msg

    def test_follow_outro_no_title(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_FOLLOW,
            outro_title="",
        )
        valid, msg = c.validate()
        assert valid is False
        assert "title" in msg

    def test_follow_outro_with_title_valid(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_FOLLOW,
            outro_title="关注",
        )
        valid, _ = c.validate()
        assert valid is True

    def test_zero_intro_duration_invalid(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_duration=0.0,
        )
        valid, msg = c.validate()
        assert valid is False
        assert "片头时长" in msg

    def test_negative_outro_duration_invalid(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
            outro_duration=-1.0,
        )
        valid, msg = c.validate()
        assert valid is False
        assert "片尾时长" in msg

    def test_negative_transition_duration_invalid(self):
        c = IntroOutroConfig(enabled=True, transition_duration=-0.5)
        valid, msg = c.validate()
        assert valid is False
        assert "转场" in msg

    def test_zero_transition_valid(self):
        c = IntroOutroConfig(enabled=True, transition_duration=0.0)
        valid, _ = c.validate()
        assert valid is True

    def test_zero_title_size_invalid(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_title_size=0,
        )
        valid, msg = c.validate()
        assert valid is False
        assert "标题字号" in msg

    def test_negative_subtitle_size_invalid(self):
        c = IntroOutroConfig(
            enabled=True,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
            outro_subtitle_size=-1,
        )
        valid, msg = c.validate()
        assert valid is False
        assert "副标题字号" in msg

    def test_full_valid_config(self):
        c = IntroOutroConfig(
            enabled=True,
            intro_type=INTRO_OUTRO_TYPE_TEXT,
            intro_title="Hi",
            intro_duration=3.0,
            intro_title_size=48,
            intro_subtitle_size=24,
            outro_type=INTRO_OUTRO_TYPE_TEXT,
            outro_title="Bye",
            outro_duration=3.0,
            outro_title_size=48,
            outro_subtitle_size=24,
            transition_duration=0.5,
        )
        valid, msg = c.validate()
        assert valid is True
        assert msg == ""
