"""GenerationTask 领域模型状态机单元测试.

覆盖：
- 初始状态为 pending
- mark_processing: pending → running
- mark_completed: running → completed
- mark_failed: pending/running → failed
- mark_cancelled: pending/running → cancelled
- mark_pending_from_failed: failed → pending（重试）
- 非法状态转换抛出 ValueError
- is_terminal / is_completed / is_failed / is_running 属性
- 状态转换时的时间戳设置
"""

from __future__ import annotations

import pytest

from packages.domain.generation_task import (
    TERMINAL_STATUSES,
    GenerationTask,
    GenerationTaskStatus,
)


def _make_task(**overrides) -> GenerationTask:
    """创建一个测试用的 GenerationTask。"""
    defaults = dict(
        id="task-test-001",
        project_id="proj-1",
        asset_library_id="lib-1",
    )
    defaults.update(overrides)
    return GenerationTask(**defaults)


# ── 初始状态 ──────────────────────────────────────────────────────────────────


class TestInitialState:
    """测试初始状态。"""

    def test_default_status_is_pending(self) -> None:
        """新创建的任务默认状态为 pending。"""
        task = _make_task()
        assert task.status == GenerationTaskStatus.PENDING
        assert task.progress == 0.0
        assert task.result_count == 0
        assert task.error_message == ""
        assert task.started_at is None
        assert task.completed_at is None

    def test_create_factory_returns_pending(self) -> None:
        """GenerationTask.create() 返回的任务状态为 pending。"""
        task = GenerationTask.create(
            project_id="proj-1",
            asset_library_id="lib-1",
            created_by_user_id="user-1",
        )
        assert task.status == GenerationTaskStatus.PENDING
        assert task.progress == 0.0
        assert task.result_count == 0

    def test_is_not_terminal_initially(self) -> None:
        """初始状态不是终态。"""
        task = _make_task()
        assert not task.is_terminal
        assert not task.is_completed
        assert not task.is_failed
        assert not task.is_running

    def test_terminal_statuses_constant(self) -> None:
        """终态集合包含 completed / failed / cancelled。"""
        assert GenerationTaskStatus.COMPLETED in TERMINAL_STATUSES
        assert GenerationTaskStatus.FAILED in TERMINAL_STATUSES
        assert GenerationTaskStatus.CANCELLED in TERMINAL_STATUSES
        assert GenerationTaskStatus.PENDING not in TERMINAL_STATUSES
        assert GenerationTaskStatus.RUNNING not in TERMINAL_STATUSES


# ── mark_processing ──────────────────────────────────────────────────────────


class TestMarkProcessing:
    """测试 pending → running 转换。"""

    def test_pending_to_running_success(self) -> None:
        """pending 状态的任务可以标记为 running。"""
        task = _make_task()
        task.mark_processing()
        assert task.status == GenerationTaskStatus.RUNNING
        assert task.is_running
        assert task.started_at is not None
        assert task.error_message == ""

    def test_started_at_is_set(self) -> None:
        """mark_processing 设置 started_at 时间戳。"""
        task = _make_task()
        assert task.started_at is None
        task.mark_processing()
        assert task.started_at is not None

    def test_error_message_cleared(self) -> None:
        """mark_processing 清除 error_message（如果有的话）。"""
        task = _make_task()
        # 注意：pending 状态通常没有 error_message，这里验证确保被清除
        task.error_message = "some old error"
        # 直接设置状态绕过校验（模拟异常场景）
        task.status = GenerationTaskStatus.PENDING
        task.mark_processing()
        assert task.error_message == ""

    def test_running_to_running_raises(self) -> None:
        """running 状态不能再次 mark_processing。"""
        task = _make_task()
        task.mark_processing()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_processing()

    def test_completed_to_running_raises(self) -> None:
        """completed 状态不能回到 running。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_processing()

    def test_failed_to_running_raises(self) -> None:
        """failed 状态不能直接到 running（应先重置为 pending）。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("some error")
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_processing()


# ── mark_completed ───────────────────────────────────────────────────────────


class TestMarkCompleted:
    """测试 running → completed 转换。"""

    def test_running_to_completed_success(self) -> None:
        """running 状态的任务可以标记为 completed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        assert task.status == GenerationTaskStatus.COMPLETED
        assert task.is_completed
        assert task.is_terminal
        assert task.completed_at is not None

    def test_progress_set_to_100(self) -> None:
        """mark_completed 设置 progress 为 100.0。"""
        task = _make_task()
        task.mark_processing()
        task.progress = 50.0  # 模拟中间进度
        task.mark_completed()
        assert task.progress == 100.0

    def test_default_result_count_is_1(self) -> None:
        """默认 result_count 为 1。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        assert task.result_count == 1

    def test_custom_result_count(self) -> None:
        """可以指定 result_count。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed(result_count=5)
        assert task.result_count == 5

    def test_error_message_cleared(self) -> None:
        """mark_completed 清除 error_message。"""
        task = _make_task()
        task.mark_processing()
        task.error_message = "temporary error"
        task.mark_completed()
        assert task.error_message == ""

    def test_completed_at_is_set(self) -> None:
        """mark_completed 设置 completed_at。"""
        task = _make_task()
        task.mark_processing()
        assert task.completed_at is None
        task.mark_completed()
        assert task.completed_at is not None

    def test_pending_to_completed_raises(self) -> None:
        """pending 状态不能直接到 completed。"""
        task = _make_task()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_completed()

    def test_completed_to_completed_raises(self) -> None:
        """completed 状态不能再次 mark_completed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_completed()

    def test_failed_to_completed_raises(self) -> None:
        """failed 状态不能直接到 completed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("error")
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_completed()


# ── mark_failed ──────────────────────────────────────────────────────────────


class TestMarkFailed:
    """测试 pending/running → failed 转换。"""

    def test_pending_to_failed_success(self) -> None:
        """pending 状态可以直接标记为 failed。"""
        task = _make_task()
        task.mark_failed("资源不足")
        assert task.status == GenerationTaskStatus.FAILED
        assert task.is_failed
        assert task.is_terminal
        assert task.error_message == "资源不足"
        assert task.completed_at is not None

    def test_running_to_failed_success(self) -> None:
        """running 状态可以标记为 failed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("生成失败：FFmpeg 错误")
        assert task.status == GenerationTaskStatus.FAILED
        assert task.is_failed
        assert task.is_terminal
        assert task.error_message == "生成失败：FFmpeg 错误"
        assert task.completed_at is not None

    def test_completed_to_failed_raises(self) -> None:
        """completed 状态不能标记为 failed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_failed("late error")

    def test_failed_to_failed_raises(self) -> None:
        """failed 状态不能再次 mark_failed。"""
        task = _make_task()
        task.mark_failed("first error")
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_failed("second error")

    def test_error_message_preserved(self) -> None:
        """错误信息被正确保存。"""
        task = _make_task()
        error_msg = "FFmpeg returned non-zero exit status 1"
        task.mark_failed(error_msg)
        assert task.error_message == error_msg


# ── mark_cancelled ───────────────────────────────────────────────────────────


class TestMarkCancelled:
    """测试 pending/running → cancelled 转换。"""

    def test_pending_to_cancelled_success(self) -> None:
        """pending 状态可以取消。"""
        task = _make_task()
        task.mark_cancelled()
        assert task.status == GenerationTaskStatus.CANCELLED
        assert task.is_terminal
        assert task.completed_at is not None

    def test_running_to_cancelled_success(self) -> None:
        """running 状态可以取消。"""
        task = _make_task()
        task.mark_processing()
        task.mark_cancelled()
        assert task.status == GenerationTaskStatus.CANCELLED
        assert task.is_terminal

    def test_completed_to_cancelled_raises(self) -> None:
        """completed 状态不能取消。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_cancelled()

    def test_failed_to_cancelled_raises(self) -> None:
        """failed 状态不能取消。"""
        task = _make_task()
        task.mark_failed("some error")
        with pytest.raises(ValueError, match="非法状态转换"):
            task.mark_cancelled()


# ── mark_pending_from_failed (重试) ─────────────────────────────────────────


class TestMarkPendingFromFailed:
    """测试 failed → pending（重试）转换。"""

    def test_failed_to_pending_success(self) -> None:
        """failed 状态可以重置为 pending（用于重试）。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("临时错误")
        task.mark_pending_from_failed()
        assert task.status == GenerationTaskStatus.PENDING
        assert not task.is_terminal
        assert task.error_message == ""
        assert task.started_at is None
        assert task.completed_at is None
        assert task.progress == 0.0
        assert task.result_count == 0

    def test_pending_to_pending_raises(self) -> None:
        """pending 状态不能调用 mark_pending_from_failed。"""
        task = _make_task()
        with pytest.raises(ValueError, match="只有 failed 状态"):
            task.mark_pending_from_failed()

    def test_running_to_pending_raises(self) -> None:
        """running 状态不能调用 mark_pending_from_failed。"""
        task = _make_task()
        task.mark_processing()
        with pytest.raises(ValueError, match="只有 failed 状态"):
            task.mark_pending_from_failed()

    def test_completed_to_pending_raises(self) -> None:
        """completed 状态不能调用 mark_pending_from_failed。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError, match="只有 failed 状态"):
            task.mark_pending_from_failed()


# ── transition_to 通用方法 ───────────────────────────────────────────────────


class TestTransitionTo:
    """测试通用的 transition_to 方法。"""

    def test_string_status_conversion(self) -> None:
        """可以传入字符串形式的状态。"""
        task = _make_task()
        task.transition_to("running")
        assert task.status == GenerationTaskStatus.RUNNING

    def test_invalid_string_raises(self) -> None:
        """无效的状态字符串抛出 ValueError。"""
        task = _make_task()
        with pytest.raises(ValueError, match="非法状态转换"):
            task.transition_to("invalid_status")

    def test_enum_status(self) -> None:
        """可以传入枚举形式的状态。"""
        task = _make_task()
        task.transition_to(GenerationTaskStatus.RUNNING)
        assert task.status == GenerationTaskStatus.RUNNING

    def test_error_message_includes_allowed_statuses(self) -> None:
        """错误信息包含允许的状态列表。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        with pytest.raises(ValueError) as exc_info:
            task.transition_to(GenerationTaskStatus.RUNNING)
        assert "completed" in str(exc_info.value)
        assert "running" in str(exc_info.value)


# ── 完整流转路径 ─────────────────────────────────────────────────────────────


class TestFullFlow:
    """测试完整的状态流转路径。"""

    def test_happy_path(self) -> None:
        """正常路径：pending → running → completed。"""
        task = _make_task()
        assert task.status == GenerationTaskStatus.PENDING
        assert not task.is_terminal

        task.mark_processing()
        assert task.status == GenerationTaskStatus.RUNNING
        assert task.started_at is not None
        assert not task.is_terminal

        task.mark_completed(result_count=3)
        assert task.status == GenerationTaskStatus.COMPLETED
        assert task.is_completed
        assert task.is_terminal
        assert task.completed_at is not None
        assert task.result_count == 3
        assert task.progress == 100.0

    def test_failure_path_from_running(self) -> None:
        """失败路径：pending → running → failed。"""
        task = _make_task()
        task.mark_processing()
        assert task.is_running

        task.mark_failed("网络超时")
        assert task.is_failed
        assert task.is_terminal
        assert task.error_message == "网络超时"
        assert task.completed_at is not None

    def test_failure_path_from_pending(self) -> None:
        """失败路径：pending → failed（启动前校验失败等）。"""
        task = _make_task()
        task.mark_failed("参数校验失败")
        assert task.is_failed
        assert task.is_terminal

    def test_retry_path(self) -> None:
        """重试路径：pending → running → failed → pending → running → completed。"""
        task = _make_task()

        # 第一次尝试失败
        task.mark_processing()
        task.mark_failed("临时错误")
        assert task.is_failed

        # 重试
        task.mark_pending_from_failed()
        assert task.status == GenerationTaskStatus.PENDING
        assert task.error_message == ""

        # 第二次成功
        task.mark_processing()
        task.mark_completed()
        assert task.is_completed

    def test_cancel_from_pending(self) -> None:
        """取消路径：pending → cancelled。"""
        task = _make_task()
        task.mark_cancelled()
        assert task.status == GenerationTaskStatus.CANCELLED
        assert task.is_terminal

    def test_cancel_from_running(self) -> None:
        """取消路径：pending → running → cancelled。"""
        task = _make_task()
        task.mark_processing()
        task.mark_cancelled()
        assert task.status == GenerationTaskStatus.CANCELLED
        assert task.is_terminal


# ── 错误信息与重试（任务中心升级） ──────────────────────────────────────────


class TestErrorInfo:
    """测试 error_info 结构化错误信息。"""

    def test_mark_failed_default_error_info(self) -> None:
        """mark_failed 不传 error_info 时自动生成默认结构。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("something went wrong")
        assert task.is_failed
        assert task.error_message == "something went wrong"
        assert task.error_info["error_type"] == "UnknownError"
        assert task.error_info["message"] == "something went wrong"
        assert "failed_at" in task.error_info

    def test_mark_failed_with_custom_error_info(self) -> None:
        """mark_failed 传自定义 error_info。"""
        task = _make_task()
        task.mark_processing()
        info = {
            "error_type": "FFmpegError",
            "message": "Invalid data found",
            "stack_trace": "Traceback...",
            "stage": "render",
            "failed_at": "2026-01-01T00:00:00+00:00",
        }
        task.mark_failed("Invalid data found", error_info=info)
        assert task.error_info == info

    def test_error_info_cleared_on_retry(self) -> None:
        """重试时 error_info 被清空。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("oops")
        assert task.error_info  # 失败时有值
        task.mark_pending_from_failed()
        assert task.error_info == {}
        assert task.status == GenerationTaskStatus.PENDING


class TestRetryCount:
    """测试 retry_count 重试次数。"""

    def test_default_retry_count_is_zero(self) -> None:
        """新任务 retry_count 默认 0。"""
        task = _make_task()
        assert task.retry_count == 0

    def test_retry_increments_count(self) -> None:
        """每次失败后重试，retry_count +1。"""
        task = _make_task()
        task.mark_processing()
        task.mark_failed("fail 1")
        task.mark_pending_from_failed()
        assert task.retry_count == 1

        task.mark_processing()
        task.mark_failed("fail 2")
        task.mark_pending_from_failed()
        assert task.retry_count == 2

    def test_completed_does_not_affect_retry_count(self) -> None:
        """正常完成不改变 retry_count。"""
        task = _make_task()
        task.mark_processing()
        task.mark_completed()
        assert task.retry_count == 0


class TestAutoRetryConfig:
    """测试自动重试配置。"""

    def test_default_auto_retry_disabled(self) -> None:
        """默认关闭自动重试。"""
        task = _make_task()
        assert task.auto_retry_enabled is False
        assert task.auto_retry_max == 0

    def test_create_with_auto_retry(self) -> None:
        """create 工厂方法支持 auto_retry 参数。"""
        task = GenerationTask.create(
            project_id="proj-1",
            asset_library_id="lib-1",
            auto_retry_enabled=True,
            auto_retry_max=3,
        )
        assert task.auto_retry_enabled is True
        assert task.auto_retry_max == 3

    def test_auto_retry_max_default_zero(self) -> None:
        """auto_retry_max 默认 0 表示不自动重试。"""
        task = GenerationTask.create(
            project_id="proj-1",
            asset_library_id="lib-1",
            auto_retry_enabled=True,
        )
        assert task.auto_retry_enabled is True
        assert task.auto_retry_max == 0
