"""intro_outro_config 模块单测 — 纯逻辑，无外部依赖."""

from __future__ import annotations

import pytest

from packages.domain.intro_outro_config import (
    INTRO_OUTRO_TYPE_FOLLOW,
    INTRO_OUTRO_TYPE_NONE,
    INTRO_OUTRO_TYPE_TEXT,
    INTRO_OUTRO_TYPE_VIDEO,
    TRANSITION_FADE,
    IntroOutroConfig,
)

# ── 默认值 ────────────────────────────────────────────────────────────────────


class TestIntroOutroConfigDefaults:
    def test_default_disabled(self):
        cfg = IntroOutroConfig()
        assert cfg.enabled is False
        assert cfg.intro_type == INTRO_OUTRO_TYPE_NONE
        assert cfg.outro_type == INTRO_OUTRO_TYPE_NONE
        assert cfg.transition_effect == TRANSITION_FADE
        assert cfg.transition_duration == 0.5

    def test_default_intro_text(self):
        cfg = IntroOutroConfig()
        assert cfg.intro_background == "#000000"
        assert cfg.intro_title == ""
        assert cfg.intro_subtitle == ""
        assert cfg.intro_title_color == "white"
        assert cfg.intro_title_size == 48
        assert cfg.intro_subtitle_color == "gray"
        assert cfg.intro_subtitle_size == 24
        assert cfg.intro_duration == 3.0

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


# ── from_dict ────────────────────────────────────────────────────────────────


class TestIntroOutroConfigFromDict:
    def test_none_data_disabled(self):
        cfg = IntroOutroConfig.from_dict(None)
        assert cfg.enabled is False

    def test_empty_dict_disabled(self):
        cfg = IntroOutroConfig.from_dict({})
        assert cfg.enabled is False

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

    def test_enabled_but_no_intro_outro(self):
        cfg = IntroOutroConfig.from_dict({"enabled": True})
        assert cfg.enabled is True
        assert cfg.has_intro is False
        assert cfg.has_outro is False

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

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

    def test_intro_video_path_alias(self):
        # video 和 video_path 都支持
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"type": "video", "video": "/tmp/a.mp4"},
            }
        )
        assert cfg.intro_video_path == "/tmp/a.mp4"

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

    def test_outro_text(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {
                    "type": "text",
                    "title": "谢谢观看",
                    "subtitle": "下期再见",
                },
            }
        )
        assert cfg.outro_type == "text"
        assert cfg.outro_title == "谢谢观看"
        assert cfg.outro_subtitle == "下期再见"
        assert cfg.has_outro is True

    def test_outro_follow(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"type": "follow", "title": "关注我"},
            }
        )
        assert cfg.outro_type == "follow"
        assert cfg.has_outro is True

    def test_outro_default_title_when_empty(self):
        # 空字符串标题会回退到默认值
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"type": "text", "title": ""},
            }
        )
        assert cfg.outro_title == "感谢观看"

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

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

    def test_invalid_intro_duration_fallback(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"type": "text", "title": "hi", "duration": "bad"},
            }
        )
        assert cfg.intro_duration == 3.0

    def test_invalid_outro_duration_fallback(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "outro": {"type": "text", "title": "hi", "duration": "bad"},
            }
        )
        assert cfg.outro_duration == 3.0

    def test_invalid_title_size_fallback(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {"type": "text", "title": "hi", "title_size": "bad"},
            }
        )
        assert cfg.intro_title_size == 48

    def test_invalid_transition_duration_fallback(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "transition_duration": "bad",
            }
        )
        assert cfg.transition_duration == 0.5

    def test_intro_is_none_dict(self):
        # intro 可能是 None
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": None,
                "outro": None,
            }
        )
        assert cfg.enabled is True
        assert cfg.intro_type == "none"

    def test_full_config(self):
        cfg = IntroOutroConfig.from_dict(
            {
                "enabled": True,
                "intro": {
                    "type": "text",
                    "title": "开场",
                    "subtitle": "精彩马上开始",
                    "background": "#123456",
                    "title_color": "white",
                    "title_size": 72,
                    "subtitle_color": "gray",
                    "subtitle_size": 28,
                    "duration": 2.0,
                },
                "outro": {
                    "type": "text",
                    "title": "结束",
                    "subtitle": "再见",
                    "background": "#654321",
                    "duration": 3.5,
                },
                "transition": "wipe",
                "transition_duration": 0.8,
            }
        )
        assert cfg.has_intro is True
        assert cfg.has_outro is True
        assert cfg.intro_title == "开场"
        assert cfg.outro_title == "结束"
        assert cfg.transition_effect == "wipe"
        assert cfg.transition_duration == 0.8


# ── has_intro / has_outro ────────────────────────────────────────────────────


class TestHasIntroHasOutro:
    def test_disabled_no_intro_outro(self):
        cfg = IntroOutroConfig(enabled=False)
        assert cfg.has_intro is False
        assert cfg.has_outro is False

    def test_enabled_none_type(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="none",
            outro_type="none",
        )
        assert cfg.has_intro is False
        assert cfg.has_outro is False

    def test_intro_video_type(self):
        cfg = IntroOutroConfig(enabled=True, intro_type="video", intro_video_path="a.mp4")
        assert cfg.has_intro is True

    def test_intro_text_type(self):
        cfg = IntroOutroConfig(enabled=True, intro_type="text", intro_title="Hi")
        assert cfg.has_intro is True

    def test_outro_video(self):
        cfg = IntroOutroConfig(enabled=True, outro_type="video", outro_video_path="a.mp4")
        assert cfg.has_outro is True

    def test_outro_text(self):
        cfg = IntroOutroConfig(enabled=True, outro_type="text", outro_title="Bye")
        assert cfg.has_outro is True

    def test_outro_follow(self):
        cfg = IntroOutroConfig(enabled=True, outro_type="follow", outro_title="Follow")
        assert cfg.has_outro is True

    def test_intro_follow_not_valid(self):
        # intro 不支持 follow 类型
        cfg = IntroOutroConfig(enabled=True, intro_type="follow")
        assert cfg.has_intro is False


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


class TestTotalExtraDuration:
    def test_disabled_zero(self):
        cfg = IntroOutroConfig(enabled=False)
        assert cfg.total_extra_duration == 0.0

    def test_both_intro_outro(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="Hi",
            intro_duration=2.0,
            outro_type="text",
            outro_title="Bye",
            outro_duration=3.0,
        )
        assert cfg.total_extra_duration == 5.0

    def test_only_intro(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="video",
            intro_video_path="a.mp4",
            intro_duration=2.5,
        )
        assert cfg.total_extra_duration == 2.5

    def test_only_outro(self):
        cfg = IntroOutroConfig(
            enabled=True,
            outro_type="video",
            outro_video_path="a.mp4",
            outro_duration=4.0,
        )
        assert cfg.total_extra_duration == 4.0

    def test_zero_duration_ignored(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="Hi",
            intro_duration=0.0,
            outro_type="text",
            outro_title="Bye",
            outro_duration=0.0,
        )
        assert cfg.total_extra_duration == 0.0


# ── validate ─────────────────────────────────────────────────────────────────


class TestValidate:
    def test_disabled_always_valid(self):
        cfg = IntroOutroConfig(enabled=False)
        ok, err = cfg.validate()
        assert ok is True
        assert err == ""

    def test_none_type_valid(self):
        cfg = IntroOutroConfig(enabled=True, intro_type="none", outro_type="none")
        ok, err = cfg.validate()
        assert ok is True

    def test_video_intro_without_path_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="video",
            intro_video_path="",
            outro_type="none",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片头" in err and "video_path" in err

    def test_text_intro_without_title_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="",
            outro_type="none",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片头" in err and "title" in err

    def test_video_outro_without_path_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="none",
            outro_type="video",
            outro_video_path="",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片尾" in err and "video_path" in err

    def test_text_outro_without_title_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="none",
            outro_type="text",
            outro_title="",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片尾" in err and "title" in err

    def test_follow_outro_without_title_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="none",
            outro_type="follow",
            outro_title="",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片尾" in err and "title" in err

    def test_zero_intro_duration_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="Hi",
            intro_duration=0,
            outro_type="none",
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片头时长" in err

    def test_negative_intro_duration_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="Hi",
        )
        object.__setattr__(cfg, "intro_duration", -1.0)
        ok, err = cfg.validate()
        assert ok is False
        assert "片头时长" in err

    def test_zero_outro_duration_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="none",
            outro_type="text",
            outro_title="Bye",
            outro_duration=0,
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "片尾时长" in err

    def test_negative_transition_duration_invalid(self):
        cfg = IntroOutroConfig(
            enabled=True,
            transition_duration=-0.5,
        )
        ok, err = cfg.validate()
        assert ok is False
        assert "转场" in err

    def test_zero_title_size_invalid(self):
        cfg = IntroOutroConfig(enabled=True)
        object.__setattr__(cfg, "intro_title_size", 0)
        ok, err = cfg.validate()
        assert ok is False
        assert "片头" in err and "字号" in err

    def test_zero_subtitle_size_invalid(self):
        cfg = IntroOutroConfig(enabled=True)
        object.__setattr__(cfg, "outro_subtitle_size", 0)
        ok, err = cfg.validate()
        assert ok is False
        assert "片尾" in err and "副标题字号" in err

    def test_valid_video_intro_outro(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="video",
            intro_video_path="/tmp/i.mp4",
            intro_duration=2.0,
            outro_type="video",
            outro_video_path="/tmp/o.mp4",
            outro_duration=3.0,
        )
        ok, err = cfg.validate()
        assert ok is True, f"expected valid but got: {err}"

    def test_valid_text_intro_outro(self):
        cfg = IntroOutroConfig(
            enabled=True,
            intro_type="text",
            intro_title="Hi",
            intro_duration=2.0,
            outro_type="text",
            outro_title="Bye",
            outro_duration=3.0,
        )
        ok, err = cfg.validate()
        assert ok is True, f"expected valid but got: {err}"

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

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