"""edit_plan_clip 领域模型单元测试."""

from datetime import datetime, timezone

import pytest
from domain.edit_plan_clip import EditPlanClip, EditPlanClipStatus


class TestEditPlanClipStatus:
    """EditPlanClipStatus 枚举测试."""

    def test_values(self):
        assert EditPlanClipStatus.PENDING == "pending"
        assert EditPlanClipStatus.READY == "ready"
        assert EditPlanClipStatus.RENDERED == "rendered"
        assert EditPlanClipStatus.FAILED == "failed"


class TestEditPlanClipCreate:
    """EditPlanClip.create 工厂方法测试."""

    def test_create_with_required_fields(self):
        clip = EditPlanClip.create(plan_id="plan_001", clip_type="video", order=1)
        assert clip.id  # 自动生成的 UUID
        assert len(clip.id) == 32  # hex 格式
        assert clip.plan_id == "plan_001"
        assert clip.clip_type == "video"
        assert clip.order == 1
        assert clip.status == EditPlanClipStatus.PENDING
        assert clip.start_time == 0.0
        assert clip.duration == 0.0
        assert clip.transition_effect == "cut"
        assert clip.playback_speed == 1.0
        assert clip.config == {}

    def test_create_with_all_fields(self):
        clip = EditPlanClip.create(
            plan_id="plan_002",
            clip_type="audio",
            order=2,
            template_clip_config_id="tpl_001",
            asset_id="asset_001",
            text_content="测试文案",
            start_time=5.0,
            duration=10.0,
            transition_effect="fade",
            transition_duration=0.5,
            playback_speed=1.5,
            config={"key": "value"},
        )
        assert clip.plan_id == "plan_002"
        assert clip.clip_type == "audio"
        assert clip.order == 2
        assert clip.template_clip_config_id == "tpl_001"
        assert clip.asset_id == "asset_001"
        assert clip.text_content == "测试文案"
        assert clip.start_time == 5.0
        assert clip.duration == 10.0
        assert clip.transition_effect == "fade"
        assert clip.transition_duration == 0.5
        assert clip.playback_speed == 1.5
        assert clip.config == {"key": "value"}

    def test_create_strips_strings(self):
        clip = EditPlanClip.create(
            plan_id="  plan_003  ",
            clip_type="  video  ",
            order=1,
            asset_id="  asset_001  ",
            template_clip_config_id="  tpl_001  ",
            text_content="  测试  ",
            transition_effect="  fade  ",
        )
        assert clip.plan_id == "plan_003"
        assert clip.clip_type == "video"
        assert clip.asset_id == "asset_001"
        assert clip.template_clip_config_id == "tpl_001"
        assert clip.text_content == "测试"
        assert clip.transition_effect == "fade"

    def test_create_empty_plan_id_raises(self):
        with pytest.raises(ValueError, match="plan_id"):
            EditPlanClip.create(plan_id="", clip_type="video", order=1)

    def test_create_whitespace_plan_id_raises(self):
        with pytest.raises(ValueError, match="plan_id"):
            EditPlanClip.create(plan_id="   ", clip_type="video", order=1)

    def test_create_empty_clip_type_raises(self):
        with pytest.raises(ValueError, match="clip_type"):
            EditPlanClip.create(plan_id="plan_001", clip_type="", order=1)

    def test_create_negative_start_time_raises(self):
        with pytest.raises(ValueError, match="start_time"):
            EditPlanClip.create(plan_id="p", clip_type="v", order=1, start_time=-1.0)

    def test_create_negative_duration_raises(self):
        with pytest.raises(ValueError, match="duration"):
            EditPlanClip.create(plan_id="p", clip_type="v", order=1, duration=-5.0)

    def test_create_zero_speed_clamps_to_1(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=0.0)
        assert clip.playback_speed == 1.0

    def test_create_negative_speed_clamps_to_1(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=-1.0)
        assert clip.playback_speed == 1.0

    def test_create_low_speed_clamps_to_min(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=0.1)
        assert clip.playback_speed == 0.25

    def test_create_high_speed_clamps_to_max(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=5.0)
        assert clip.playback_speed == 4.0

    def test_create_speed_at_boundary_values(self):
        # 边界值应该保持不变
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=0.25)
        assert clip.playback_speed == 0.25

        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, playback_speed=4.0)
        assert clip.playback_speed == 4.0

    def test_create_negative_transition_duration_clamps_to_0(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, transition_duration=-1.0)
        assert clip.transition_duration == 0.0

    def test_create_empty_transition_effect_defaults_to_cut(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, transition_effect="")
        assert clip.transition_effect == "cut"

    def test_create_empty_asset_id_stays_empty(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, asset_id="")
        assert clip.asset_id == ""

    def test_create_none_config_defaults_to_empty_dict(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, config=None)
        assert clip.config == {}

    def test_create_ids_are_unique(self):
        c1 = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        c2 = EditPlanClip.create(plan_id="p", clip_type="v", order=2)
        assert c1.id != c2.id

    def test_create_timestamps_are_utc(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        assert clip.created_at.tzinfo is not None
        assert clip.updated_at.tzinfo is not None


class TestEditPlanClipStateMachine:
    """状态机流转测试."""

    @pytest.fixture
    def pending_clip(self):
        return EditPlanClip.create(plan_id="plan_001", clip_type="video", order=1)

    def test_initial_status_is_pending(self, pending_clip):
        assert pending_clip.status == EditPlanClipStatus.PENDING

    def test_pending_to_ready(self, pending_clip):
        pending_clip.mark_ready()
        assert pending_clip.status == EditPlanClipStatus.READY

    def test_pending_cannot_mark_rendered(self, pending_clip):
        with pytest.raises(ValueError, match="只有 ready"):
            pending_clip.mark_rendered()

    def test_pending_cannot_mark_failed(self, pending_clip):
        with pytest.raises(ValueError, match="只有 ready"):
            pending_clip.mark_failed()

    def test_ready_to_rendered(self, pending_clip):
        pending_clip.mark_ready()
        pending_clip.mark_rendered()
        assert pending_clip.status == EditPlanClipStatus.RENDERED

    def test_ready_to_failed(self, pending_clip):
        pending_clip.mark_ready()
        pending_clip.mark_failed()
        assert pending_clip.status == EditPlanClipStatus.FAILED

    def test_rendered_cannot_mark_ready_again(self, pending_clip):
        pending_clip.mark_ready()
        pending_clip.mark_rendered()
        with pytest.raises(ValueError):
            pending_clip.mark_ready()

    def test_failed_cannot_mark_ready_again(self, pending_clip):
        pending_clip.mark_ready()
        pending_clip.mark_failed()
        with pytest.raises(ValueError):
            pending_clip.mark_ready()

    def test_state_transition_updates_updated_at(self, pending_clip):
        old_updated = pending_clip.updated_at
        # 确保时间不同
        import time

        time.sleep(0.001)
        pending_clip.mark_ready()
        assert pending_clip.updated_at > old_updated


class TestEditPlanClipAssignAsset:
    """assign_asset 方法测试."""

    def test_assign_asset(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        assert not clip.has_asset
        clip.assign_asset("asset_001")
        assert clip.asset_id == "asset_001"
        assert clip.has_asset

    def test_assign_asset_strips_whitespace(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        clip.assign_asset("  asset_001  ")
        assert clip.asset_id == "asset_001"

    def test_assign_empty_asset_raises(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        with pytest.raises(ValueError, match="asset_id"):
            clip.assign_asset("")

    def test_assign_whitespace_asset_raises(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        with pytest.raises(ValueError, match="asset_id"):
            clip.assign_asset("   ")

    def test_assign_updates_updated_at(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        old_updated = clip.updated_at
        import time

        time.sleep(0.001)
        clip.assign_asset("asset_001")
        assert clip.updated_at > old_updated


class TestEditPlanClipProperties:
    """属性方法测试."""

    def test_end_time(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, start_time=5.0, duration=10.0)
        assert clip.end_time == 15.0

    def test_end_time_zero_duration(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, start_time=3.0, duration=0.0)
        assert clip.end_time == 3.0

    def test_has_asset_true(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, asset_id="a001")
        assert clip.has_asset is True

    def test_has_asset_false(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1)
        assert clip.has_asset is False

    def test_has_asset_empty_string(self):
        clip = EditPlanClip.create(plan_id="p", clip_type="v", order=1, asset_id="")
        assert clip.has_asset is False
