"""edit_plan 单测.

domain 层剪辑计划纯逻辑模块，0 外部依赖。
覆盖：枚举常量、create工厂/校验、完整状态流转、重置。
"""

from __future__ import annotations

from packages.domain.edit_plan import EditPlan, EditPlanStatus


class TestEditPlanStatus:
    """EditPlanStatus 枚举测试."""

    def test_five_statuses(self):
        """五种状态."""
        assert len(EditPlanStatus) == 5

    def test_draft(self):
        assert EditPlanStatus.DRAFT == "draft"

    def test_editing(self):
        assert EditPlanStatus.EDITING == "editing"

    def test_rendering(self):
        assert EditPlanStatus.RENDERING == "rendering"

    def test_completed(self):
        assert EditPlanStatus.COMPLETED == "completed"

    def test_failed(self):
        assert EditPlanStatus.FAILED == "failed"


class TestEditPlanCreate:
    """EditPlan.create 工厂测试."""

    def test_create_minimal(self):
        """最简创建."""
        plan = EditPlan.create(template_id="tpl1", name="  我的计划  ")
        assert plan.template_id == "tpl1"
        assert plan.name == "我的计划"
        assert plan.status == EditPlanStatus.DRAFT
        assert plan.total_duration == 0.0
        assert plan.config == {}
        assert plan.project_id == ""
        assert plan.created_by_user_id == ""
        assert isinstance(plan.id, str)
        assert len(plan.id) > 0

    def test_create_full(self):
        """带全部字段."""
        plan = EditPlan.create(
            template_id="tpl1",
            name="口播视频计划",
            config={"bgm": "rock"},
            total_duration=120.5,
            source_edit_plan_id="src1",
            project_id="proj1",
            created_by_user_id="user1",
        )
        assert plan.name == "口播视频计划"
        assert plan.total_duration == 120.5
        assert plan.source_edit_plan_id == "src1"
        assert plan.project_id == "proj1"
        assert plan.created_by_user_id == "user1"
        assert plan.config == {"bgm": "rock"}

    def test_create_empty_name(self):
        """空名称无效."""
        try:
            EditPlan.create(template_id="t1", name="")
            assert False
        except ValueError as e:
            assert "名称" in str(e)

    def test_create_whitespace_name(self):
        """空白名称无效."""
        try:
            EditPlan.create(template_id="t1", name="   ")
            assert False
        except ValueError as e:
            assert "名称" in str(e)

    def test_create_empty_template_id(self):
        """空 template_id 无效."""
        try:
            EditPlan.create(template_id="", name="计划")
            assert False
        except ValueError as e:
            assert "template_id" in str(e)

    def test_create_whitespace_template_id(self):
        """空白 template_id 无效."""
        try:
            EditPlan.create(template_id="   ", name="计划")
            assert False
        except ValueError as e:
            assert "template_id" in str(e)

    def test_config_none_defaults_empty(self):
        """config=None 默认为空 dict."""
        plan = EditPlan.create(template_id="t1", name="p", config=None)
        assert plan.config == {}

    def test_unique_id(self):
        """不同计划 id 不同."""
        p1 = EditPlan.create("t", "p1")
        p2 = EditPlan.create("t", "p2")
        assert p1.id != p2.id

    def test_has_timestamps(self):
        """有创建和更新时间."""
        plan = EditPlan.create("t", "p")
        assert plan.created_at is not None
        assert plan.updated_at is not None

    def test_strips_string_fields(self):
        """字符串字段 strip."""
        plan = EditPlan.create(
            template_id="  t1  ",
            name="  n  ",
            source_edit_plan_id="  s  ",
            project_id="  p  ",
            created_by_user_id="  u  ",
        )
        assert plan.template_id == "t1"
        assert plan.name == "n"
        assert plan.source_edit_plan_id == "s"
        assert plan.project_id == "p"
        assert plan.created_by_user_id == "u"


class TestEditPlanStatusFlow:
    """状态流转测试."""

    def _make_plan(self):
        return EditPlan.create(template_id="t1", name="测试计划")

    def test_initial_status_draft(self):
        """初始状态 draft."""
        plan = self._make_plan()
        assert plan.status == EditPlanStatus.DRAFT

    def test_draft_to_editing(self):
        """draft -> editing."""
        plan = self._make_plan()
        old = plan.updated_at
        plan.start_editing()
        assert plan.status == EditPlanStatus.EDITING
        assert plan.updated_at >= old

    def test_editing_to_rendering(self):
        """editing -> rendering."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        assert plan.status == EditPlanStatus.RENDERING

    def test_rendering_to_completed(self):
        """rendering -> completed."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_completed()
        assert plan.status == EditPlanStatus.COMPLETED

    def test_rendering_to_failed(self):
        """rendering -> failed."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_failed()
        assert plan.status == EditPlanStatus.FAILED

    def test_failed_to_draft_reset(self):
        """failed -> draft 重置."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_failed()
        plan.reset_to_draft()
        assert plan.status == EditPlanStatus.DRAFT

    def test_cannot_editing_from_completed(self):
        """completed 不能 start_editing."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_completed()
        try:
            plan.start_editing()
            assert False
        except ValueError as e:
            assert "draft" in str(e).lower()

    def test_cannot_rendering_from_draft(self):
        """draft 不能直接 start_rendering."""
        plan = self._make_plan()
        try:
            plan.start_rendering()
            assert False
        except ValueError as e:
            assert "editing" in str(e).lower()

    def test_cannot_complete_from_editing(self):
        """editing 不能直接完成."""
        plan = self._make_plan()
        plan.start_editing()
        try:
            plan.mark_completed()
            assert False
        except ValueError as e:
            assert "rendering" in str(e).lower()

    def test_cannot_fail_from_editing(self):
        """editing 不能直接失败."""
        plan = self._make_plan()
        plan.start_editing()
        try:
            plan.mark_failed()
            assert False
        except ValueError as e:
            assert "rendering" in str(e).lower()

    def test_cannot_reset_from_draft(self):
        """draft 不能重置."""
        plan = self._make_plan()
        try:
            plan.reset_to_draft()
            assert False
        except ValueError as e:
            assert "failed" in str(e).lower()

    def test_cannot_reset_from_completed(self):
        """completed 不能重置."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_completed()
        try:
            plan.reset_to_draft()
            assert False
        except ValueError as e:
            assert "failed" in str(e).lower()

    def test_full_happy_path(self):
        """完整成功路径."""
        plan = self._make_plan()
        assert plan.status == EditPlanStatus.DRAFT
        plan.start_editing()
        assert plan.status == EditPlanStatus.EDITING
        plan.start_rendering()
        assert plan.status == EditPlanStatus.RENDERING
        plan.mark_completed()
        assert plan.status == EditPlanStatus.COMPLETED

    def test_failed_reset_retry(self):
        """失败后重置重试完整路径."""
        plan = self._make_plan()
        plan.start_editing()
        plan.start_rendering()
        plan.mark_failed()
        assert plan.status == EditPlanStatus.FAILED
        plan.reset_to_draft()
        assert plan.status == EditPlanStatus.DRAFT
        # 重新走一遍
        plan.start_editing()
        plan.start_rendering()
        plan.mark_completed()
        assert plan.status == EditPlanStatus.COMPLETED

    def test_each_transition_updates_timestamp(self):
        """每次状态变更都更新 updated_at."""
        plan = self._make_plan()
        timestamps = [plan.updated_at]
        plan.start_editing()
        timestamps.append(plan.updated_at)
        plan.start_rendering()
        timestamps.append(plan.updated_at)
        plan.mark_completed()
        timestamps.append(plan.updated_at)
        # 单调递增
        for i in range(1, len(timestamps)):
            assert timestamps[i] >= timestamps[i - 1]


class TestEditPlanConfig:
    """config 独立性测试."""

    def test_config_independent(self):
        """不同计划的 config 独立."""
        p1 = EditPlan.create(template_id="t", name="p1")
        p2 = EditPlan.create(template_id="t", name="p2")
        p1.config["key"] = "value"
        assert "key" not in p2.config
