"""查重应用层用例单元测试。

覆盖：
- UploadForDuplicationUseCase — 创建查重记录
- ListDuplicationRecordsUseCase — 列表查询（含分页）
- GetDuplicationDetailUseCase — 详情查询
- DeleteDuplicationRecordUseCase — 删除记录
- RetryDuplicationUseCase — 重试查重（含状态校验）
"""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from packages.application.duplication import (
    DeleteDuplicationRecordUseCase,
    GetDuplicationDetailUseCase,
    ListDuplicationRecordsUseCase,
    RetryDuplicationUseCase,
    UploadForDuplicationCommand,
    UploadForDuplicationUseCase,
)
from packages.domain.duplication import DuplicateSegment, DuplicationRecord


def _make_record(status="pending", **kwargs):
    """创建测试用 DuplicationRecord。"""
    record = DuplicationRecord.create(
        user_id=kwargs.get("user_id", "user-1"),
        filename=kwargs.get("filename", "test.mp4"),
        file_size=kwargs.get("file_size", 1024),
        storage_key=kwargs.get("storage_key", "oss/key"),
        duration_seconds=kwargs.get("duration", 30.0),
    )
    if status != "pending":
        record.mark_processing()
    if status == "completed":
        record.mark_completed(duplicate_rate=15.0, duplicate_count=1, segments=[])
    elif status == "failed":
        record.mark_failed("处理失败")
    return record


class TestUploadForDuplicationUseCase:
    """上传查重用例测试。"""

    def test_execute_creates_and_persists_record(self):
        mock_repo = MagicMock()
        mock_repo.create.side_effect = lambda r: r

        use_case = UploadForDuplicationUseCase(mock_repo)
        command = UploadForDuplicationCommand(
            user_id="user-1",
            filename="video.mp4",
            file_size=2048,
            storage_key="oss/video.mp4",
            duration_seconds=60.0,
        )
        result = use_case.execute(command)

        assert result.user_id == "user-1"
        assert result.filename == "video.mp4"
        assert result.file_size == 2048
        assert result.status == "pending"
        mock_repo.create.assert_called_once()

    def test_execute_with_default_duration(self):
        mock_repo = MagicMock()
        mock_repo.create.side_effect = lambda r: r

        use_case = UploadForDuplicationUseCase(mock_repo)
        command = UploadForDuplicationCommand(
            user_id="user-1",
            filename="video.mp4",
            file_size=1024,
            storage_key="oss/key",
        )
        result = use_case.execute(command)
        assert result.duration_seconds == 0.0

    def test_execute_invalid_user_id_raises(self):
        mock_repo = MagicMock()
        use_case = UploadForDuplicationUseCase(mock_repo)
        command = UploadForDuplicationCommand(
            user_id="",
            filename="video.mp4",
            file_size=1024,
            storage_key="oss/key",
        )
        with pytest.raises(ValueError, match="user_id"):
            use_case.execute(command)
        mock_repo.create.assert_not_called()


class TestListDuplicationRecordsUseCase:
    """列表查询用例测试。"""

    def test_execute_returns_records(self):
        records = [_make_record(), _make_record(filename="b.mp4")]
        mock_repo = MagicMock()
        mock_repo.list_by_user.return_value = records

        use_case = ListDuplicationRecordsUseCase(mock_repo)
        result = use_case.execute("user-1")

        assert len(result) == 2
        mock_repo.list_by_user.assert_called_once_with("user-1", offset=0, limit=50)

    def test_execute_with_pagination(self):
        mock_repo = MagicMock()
        mock_repo.list_by_user.return_value = []

        use_case = ListDuplicationRecordsUseCase(mock_repo)
        use_case.execute("user-1", offset=10, limit=20)

        mock_repo.list_by_user.assert_called_once_with("user-1", offset=10, limit=20)

    def test_execute_empty_user_id_raises(self):
        mock_repo = MagicMock()
        use_case = ListDuplicationRecordsUseCase(mock_repo)

        with pytest.raises(ValueError):
            use_case.execute("")
        mock_repo.list_by_user.assert_not_called()

    def test_execute_whitespace_user_id_raises(self):
        mock_repo = MagicMock()
        use_case = ListDuplicationRecordsUseCase(mock_repo)

        with pytest.raises(ValueError):
            use_case.execute("   ")

    def test_execute_strips_user_id(self):
        mock_repo = MagicMock()
        mock_repo.list_by_user.return_value = []

        use_case = ListDuplicationRecordsUseCase(mock_repo)
        use_case.execute("  user-1  ")

        mock_repo.list_by_user.assert_called_once_with("user-1", offset=0, limit=50)


class TestGetDuplicationDetailUseCase:
    """详情查询用例测试。"""

    def test_execute_returns_record(self):
        record = _make_record()
        mock_repo = MagicMock()
        mock_repo.get.return_value = record

        use_case = GetDuplicationDetailUseCase(mock_repo)
        result = use_case.execute(record.id)

        assert result is record
        mock_repo.get.assert_called_once_with(record.id)

    def test_execute_returns_none_for_missing(self):
        mock_repo = MagicMock()
        mock_repo.get.return_value = None

        use_case = GetDuplicationDetailUseCase(mock_repo)
        result = use_case.execute("nonexistent")

        assert result is None


class TestDeleteDuplicationRecordUseCase:
    """删除用例测试。"""

    def test_execute_deletes_record(self):
        mock_repo = MagicMock()
        mock_repo.delete.return_value = True

        use_case = DeleteDuplicationRecordUseCase(mock_repo)
        result = use_case.execute("record-1")

        assert result is True
        mock_repo.delete.assert_called_once_with("record-1")

    def test_execute_returns_false_for_missing(self):
        mock_repo = MagicMock()
        mock_repo.delete.return_value = False

        use_case = DeleteDuplicationRecordUseCase(mock_repo)
        result = use_case.execute("nonexistent")

        assert result is False


class TestRetryDuplicationUseCase:
    """重试用例测试。"""

    def test_execute_resets_failed_record(self):
        record = _make_record(status="failed")
        mock_repo = MagicMock()
        mock_repo.get.return_value = record
        mock_repo.update.side_effect = lambda r: r

        use_case = RetryDuplicationUseCase(mock_repo)
        result = use_case.execute(record.id)

        assert result is not None
        assert result.status == "pending"
        assert result.error_message == ""
        assert result.duplicate_rate is None
        assert result.duplicate_count == 0
        assert result.segments == []
        mock_repo.update.assert_called_once()

    def test_execute_returns_none_for_missing(self):
        mock_repo = MagicMock()
        mock_repo.get.return_value = None

        use_case = RetryDuplicationUseCase(mock_repo)
        result = use_case.execute("nonexistent")

        assert result is None
        mock_repo.update.assert_not_called()

    def test_execute_calls_repo_get_and_update(self):
        record = _make_record(status="failed")
        mock_repo = MagicMock()
        mock_repo.get.return_value = record
        mock_repo.update.side_effect = lambda r: r

        use_case = RetryDuplicationUseCase(mock_repo)
        use_case.execute(record.id)

        mock_repo.get.assert_called_once_with(record.id)
        mock_repo.update.assert_called_once()
