"""template_clip_config 单测.

domain 层模板片段配置纯逻辑模块，0 外部依赖。
覆盖：ClipType枚举、TransitionEffect枚举、create工厂/校验、计算属性。
"""

from __future__ import annotations

from packages.domain.template_clip_config import (
    ClipType,
    TemplateClipConfig,
    TransitionEffect,
)


class TestClipType:
    """ClipType 枚举测试."""

    def test_six_types(self):
        """六种片段类型."""
        assert len(ClipType) == 6

    def test_intro(self):
        assert ClipType.INTRO == "intro"

    def test_main(self):
        assert ClipType.MAIN == "main"

    def test_transition(self):
        assert ClipType.TRANSITION == "transition"

    def test_outro(self):
        assert ClipType.OUTRO == "outro"

    def test_title(self):
        assert ClipType.TITLE == "title"

    def test_subtitle(self):
        assert ClipType.SUBTITLE == "subtitle"

    def test_from_string(self):
        """可从字符串构造."""
        assert ClipType("main") == ClipType.MAIN
        assert ClipType("intro") == ClipType.INTRO


class TestTransitionEffect:
    """TransitionEffect 枚举测试."""

    def test_six_effects(self):
        """六种转场效果."""
        assert len(TransitionEffect) == 6

    def test_cut(self):
        assert TransitionEffect.CUT == "cut"

    def test_fade(self):
        assert TransitionEffect.FADE == "fade"

    def test_slide_left(self):
        assert TransitionEffect.SLIDE_LEFT == "slide_left"

    def test_slide_right(self):
        assert TransitionEffect.SLIDE_RIGHT == "slide_right"

    def test_dissolve(self):
        assert TransitionEffect.DISSOLVE == "dissolve"

    def test_wipe(self):
        assert TransitionEffect.WIPE == "wipe"

    def test_from_string(self):
        """可从字符串构造."""
        assert TransitionEffect("fade") == TransitionEffect.FADE
        assert TransitionEffect("cut") == TransitionEffect.CUT


class TestTemplateClipConfigCreate:
    """TemplateClipConfig.create 工厂测试."""

    def test_create_minimal(self):
        """最简创建."""
        clip = TemplateClipConfig.create(
            template_id="tpl1",
            clip_type=ClipType.MAIN,
            order=0,
        )
        assert clip.template_id == "tpl1"
        assert clip.clip_type == ClipType.MAIN
        assert clip.order == 0
        assert clip.min_duration == 0.0
        assert clip.max_duration == 0.0
        assert clip.text_template == ""
        assert clip.material_requirements == {}
        assert clip.transition_effect == TransitionEffect.CUT
        assert clip.config == {}
        assert isinstance(clip.id, str)
        assert len(clip.id) > 0

    def test_create_with_string_clip_type(self):
        """用字符串传 clip_type."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type="intro",
            order=0,
        )
        assert clip.clip_type == ClipType.INTRO

    def test_create_with_string_transition(self):
        """用字符串传 transition_effect."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type=ClipType.MAIN,
            order=0,
            transition_effect="fade",
        )
        assert clip.transition_effect == TransitionEffect.FADE

    def test_create_full(self):
        """带全部字段."""
        clip = TemplateClipConfig.create(
            template_id="  tpl1  ",
            clip_type=ClipType.TITLE,
            order=2,
            min_duration=2.0,
            max_duration=5.0,
            text_template="  欢迎关注  ",
            material_requirements={"category": "scenic"},
            transition_effect=TransitionEffect.FADE,
            config={"font_size": 24},
        )
        assert clip.template_id == "tpl1"  # strip
        assert clip.clip_type == ClipType.TITLE
        assert clip.order == 2
        assert clip.min_duration == 2.0
        assert clip.max_duration == 5.0
        assert clip.text_template == "欢迎关注"  # strip
        assert clip.material_requirements == {"category": "scenic"}
        assert clip.transition_effect == TransitionEffect.FADE
        assert clip.config == {"font_size": 24}

    def test_create_empty_template_id(self):
        """空 template_id 无效."""
        try:
            TemplateClipConfig.create(template_id="", clip_type=ClipType.MAIN, order=0)
            assert False
        except ValueError as e:
            assert "template_id" in str(e)

    def test_create_whitespace_template_id(self):
        """空白 template_id 无效."""
        try:
            TemplateClipConfig.create(template_id="   ", clip_type=ClipType.MAIN, order=0)
            assert False
        except ValueError as e:
            assert "template_id" in str(e)

    def test_create_negative_min_duration(self):
        """min_duration 为负无效."""
        try:
            TemplateClipConfig.create(
                template_id="t1",
                clip_type=ClipType.MAIN,
                order=0,
                min_duration=-1.0,
            )
            assert False
        except ValueError as e:
            assert "min_duration" in str(e)

    def test_create_negative_max_duration(self):
        """max_duration 为负无效."""
        try:
            TemplateClipConfig.create(
                template_id="t1",
                clip_type=ClipType.MAIN,
                order=0,
                max_duration=-1.0,
            )
            assert False
        except ValueError as e:
            assert "max_duration" in str(e)

    def test_create_min_greater_than_max(self):
        """min > max 且 max > 0 时无效."""
        try:
            TemplateClipConfig.create(
                template_id="t1",
                clip_type=ClipType.MAIN,
                order=0,
                min_duration=5.0,
                max_duration=3.0,
            )
            assert False
        except ValueError as e:
            assert "min_duration" in str(e) and "max_duration" in str(e)

    def test_create_min_equals_max_valid(self):
        """min == max 合法."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type=ClipType.MAIN,
            order=0,
            min_duration=3.0,
            max_duration=3.0,
        )
        assert clip.min_duration == 3.0
        assert clip.max_duration == 3.0

    def test_create_zero_both_valid(self):
        """都为 0 合法（未设置时长）."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type=ClipType.MAIN,
            order=0,
            min_duration=0.0,
            max_duration=0.0,
        )
        assert clip.min_duration == 0.0
        assert clip.max_duration == 0.0

    def test_create_invalid_clip_type_string(self):
        """无效的 clip_type 字符串抛错."""
        try:
            TemplateClipConfig.create(template_id="t1", clip_type="invalid", order=0)
            assert False
        except ValueError:
            pass  # 枚举构造失败会抛 ValueError

    def test_create_material_req_none_defaults_empty(self):
        """material_requirements=None 默认为空 dict."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type=ClipType.MAIN,
            order=0,
            material_requirements=None,
        )
        assert clip.material_requirements == {}

    def test_create_config_none_defaults_empty(self):
        """config=None 默认为空 dict."""
        clip = TemplateClipConfig.create(
            template_id="t1",
            clip_type=ClipType.MAIN,
            order=0,
            config=None,
        )
        assert clip.config == {}

    def test_create_unique_id(self):
        """不同配置 id 不同."""
        c1 = TemplateClipConfig.create("t", ClipType.MAIN, 0)
        c2 = TemplateClipConfig.create("t", ClipType.MAIN, 0)
        assert c1.id != c2.id

    def test_create_has_timestamps(self):
        """有创建和更新时间."""
        clip = TemplateClipConfig.create("t", ClipType.MAIN, 0)
        assert clip.created_at is not None
        assert clip.updated_at is not None


class TestTemplateClipConfigProperties:
    """计算属性测试."""

    def test_has_duration_range_false_zero(self):
        """都为 0 时没有时长范围."""
        clip = TemplateClipConfig.create("t", ClipType.MAIN, 0)
        assert clip.has_duration_range is False

    def test_has_duration_range_true_min_only(self):
        """只有 min > 0 也算有范围."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            min_duration=2.0,
        )
        assert clip.has_duration_range is True

    def test_has_duration_range_true_max_only(self):
        """只有 max > 0 也算有范围."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            max_duration=5.0,
        )
        assert clip.has_duration_range is True

    def test_has_duration_range_true_both(self):
        """两者都 > 0 有范围."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            min_duration=2.0,
            max_duration=5.0,
        )
        assert clip.has_duration_range is True

    def test_default_duration_zero(self):
        """都为 0 默认时长 0."""
        clip = TemplateClipConfig.create("t", ClipType.MAIN, 0)
        assert clip.default_duration == 0.0

    def test_default_duration_middle(self):
        """两者都有取中间值."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            min_duration=2.0,
            max_duration=6.0,
        )
        assert clip.default_duration == 4.0

    def test_default_duration_min_only(self):
        """只有 min 用 min."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            min_duration=3.0,
        )
        assert clip.default_duration == 3.0

    def test_default_duration_max_only(self):
        """只有 max 用 max."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            max_duration=5.0,
        )
        assert clip.default_duration == 5.0

    def test_default_duration_equal_min_max(self):
        """min == max 时值相等."""
        clip = TemplateClipConfig.create(
            "t",
            ClipType.MAIN,
            0,
            min_duration=3.0,
            max_duration=3.0,
        )
        assert clip.default_duration == 3.0
