"""TemplateClipConfig domain entity unit tests (wave219)."""

from datetime import datetime, timezone

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

# ---------------------------------------------------------------------------
# ClipType enum
# ---------------------------------------------------------------------------


class TestClipType:
    def test_all_members_exist(self):
        assert ClipType.INTRO.value == "intro"
        assert ClipType.MAIN.value == "main"
        assert ClipType.TRANSITION.value == "transition"
        assert ClipType.OUTRO.value == "outro"
        assert ClipType.TITLE.value == "title"
        assert ClipType.SUBTITLE.value == "subtitle"

    def test_is_str_enum(self):
        # StrEnum members are instances of str
        assert isinstance(ClipType.INTRO, str)
        assert ClipType.INTRO == "intro"

    def test_from_string(self):
        assert ClipType("intro") is ClipType.INTRO
        assert ClipType("main") is ClipType.MAIN

    def test_invalid_value_raises(self):
        with pytest.raises(ValueError):
            ClipType("invalid")


# ---------------------------------------------------------------------------
# TransitionEffect enum
# ---------------------------------------------------------------------------


class TestTransitionEffect:
    def test_all_members_exist(self):
        assert TransitionEffect.CUT.value == "cut"
        assert TransitionEffect.FADE.value == "fade"
        assert TransitionEffect.SLIDE_LEFT.value == "slide_left"
        assert TransitionEffect.SLIDE_RIGHT.value == "slide_right"
        assert TransitionEffect.DISSOLVE.value == "dissolve"
        assert TransitionEffect.WIPE.value == "wipe"

    def test_is_str_enum(self):
        assert isinstance(TransitionEffect.CUT, str)
        assert TransitionEffect.CUT == "cut"

    def test_from_string(self):
        assert TransitionEffect("cut") is TransitionEffect.CUT
        assert TransitionEffect("fade") is TransitionEffect.FADE

    def test_invalid_value_raises(self):
        with pytest.raises(ValueError):
            TransitionEffect("invalid_effect")


# ---------------------------------------------------------------------------
# TemplateClipConfig.create()
# ---------------------------------------------------------------------------


class TestTemplateClipConfigCreate:
    def test_create_basic(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
        )
        assert cfg.template_id == "tpl_001"
        assert cfg.clip_type == ClipType.MAIN
        assert cfg.order == 1
        assert cfg.id  # auto-generated uuid hex
        assert len(cfg.id) == 32  # uuid4 hex length

    def test_create_with_string_clip_type(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type="intro",
            order=0,
        )
        assert cfg.clip_type == ClipType.INTRO

    def test_create_with_string_transition_effect(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
            transition_effect="fade",
        )
        assert cfg.transition_effect == TransitionEffect.FADE

    def test_create_empty_template_id_raises(self):
        with pytest.raises(ValueError, match="template_id 不能为空"):
            TemplateClipConfig.create(
                template_id="",
                clip_type=ClipType.MAIN,
                order=1,
            )

    def test_create_whitespace_template_id_raises(self):
        with pytest.raises(ValueError, match="template_id 不能为空"):
            TemplateClipConfig.create(
                template_id="   ",
                clip_type=ClipType.MAIN,
                order=1,
            )

    def test_create_template_id_stripped(self):
        cfg = TemplateClipConfig.create(
            template_id="  tpl_001  ",
            clip_type=ClipType.MAIN,
            order=1,
        )
        assert cfg.template_id == "tpl_001"

    def test_create_negative_min_duration_raises(self):
        with pytest.raises(ValueError, match="min_duration 不能为负数"):
            TemplateClipConfig.create(
                template_id="tpl_001",
                clip_type=ClipType.MAIN,
                order=1,
                min_duration=-1.0,
            )

    def test_create_negative_max_duration_raises(self):
        with pytest.raises(ValueError, match="max_duration 不能为负数"):
            TemplateClipConfig.create(
                template_id="tpl_001",
                clip_type=ClipType.MAIN,
                order=1,
                max_duration=-5.0,
            )

    def test_create_min_greater_than_max_raises(self):
        with pytest.raises(ValueError, match="min_duration 不能大于 max_duration"):
            TemplateClipConfig.create(
                template_id="tpl_001",
                clip_type=ClipType.MAIN,
                order=1,
                min_duration=10.0,
                max_duration=5.0,
            )

    def test_create_min_equals_max_ok(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
            min_duration=5.0,
            max_duration=5.0,
        )
        assert cfg.min_duration == 5.0
        assert cfg.max_duration == 5.0

    def test_create_default_values(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
        )
        assert cfg.min_duration == 0.0
        assert cfg.max_duration == 0.0
        assert cfg.text_template == ""
        assert cfg.material_requirements == {}
        assert cfg.transition_effect == TransitionEffect.CUT
        assert cfg.config == {}

    def test_create_material_requirements_none_becomes_empty_dict(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
            material_requirements=None,
        )
        assert cfg.material_requirements == {}
        # should be a fresh dict, not shared
        cfg.material_requirements["key"] = "val"
        cfg2 = TemplateClipConfig.create(
            template_id="tpl_002",
            clip_type=ClipType.MAIN,
            order=1,
        )
        assert cfg2.material_requirements == {}

    def test_create_config_none_becomes_empty_dict(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
            config=None,
        )
        assert cfg.config == {}

    def test_create_text_template_stripped(self):
        cfg = TemplateClipConfig.create(
            template_id="tpl_001",
            clip_type=ClipType.MAIN,
            order=1,
            text_template="  hello {{name}}  ",
        )
        assert cfg.text_template == "hello {{name}}"

    def test_create_generates_unique_ids(self):
        cfg1 = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
        cfg2 = TemplateClipConfig.create("tpl", ClipType.MAIN, 2)
        assert cfg1.id != cfg2.id

    def test_create_timestamps_are_utc(self):
        before = datetime.now(timezone.utc)
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
        after = datetime.now(timezone.utc)
        assert before <= cfg.created_at <= after
        assert before <= cfg.updated_at <= after
        assert cfg.created_at.tzinfo is timezone.utc
        assert cfg.updated_at.tzinfo is timezone.utc


# ---------------------------------------------------------------------------
# has_duration_range property
# ---------------------------------------------------------------------------


class TestHasDurationRange:
    def test_both_zero(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
        assert cfg.has_duration_range is False

    def test_only_min_positive(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0)
        assert cfg.has_duration_range is True

    def test_only_max_positive(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0)
        assert cfg.has_duration_range is True

    def test_both_positive(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=5.0)
        assert cfg.has_duration_range is True


# ---------------------------------------------------------------------------
# default_duration property
# ---------------------------------------------------------------------------


class TestDefaultDuration:
    def test_both_zero_returns_zero(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
        assert cfg.default_duration == 0.0

    def test_only_min_returns_min(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0)
        assert cfg.default_duration == 3.0

    def test_only_max_returns_max(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0)
        assert cfg.default_duration == 5.0

    def test_both_set_returns_midpoint(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=6.0)
        assert cfg.default_duration == 4.0

    def test_min_equals_max_returns_same(self):
        cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0, max_duration=3.0)
        assert cfg.default_duration == 3.0
