"""查重 UseCase 单元测试."""

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 DuplicationRecord


@pytest.fixture
def mock_repo():
    return MagicMock()


@pytest.fixture
def sample_record():
    r = DuplicationRecord.create(
        user_id="user_1",
        filename="test_video.mp4",
        file_size=1048576,
        storage_key="uploads/test_video.mp4",
        duration_seconds=30.5,
    )
    r.id = "dup_123"
    return r


@pytest.fixture
def failed_record():
    r = DuplicationRecord.create(
        user_id="user_1",
        filename="failed.mp4",
        file_size=512000,
        storage_key="uploads/failed.mp4",
    )
    r.id = "dup_456"
    r.mark_failed("网络超时")
    return r


class TestUploadForDuplicationUseCase:
    """UploadForDuplicationUseCase 测试"""

    def test_upload_success(self, mock_repo, sample_record):
        """上传查重成功"""
        mock_repo.create.return_value = sample_record
        use_case = UploadForDuplicationUseCase(mock_repo)

        command = UploadForDuplicationCommand(
            user_id="user_1",
            filename="test_video.mp4",
            file_size=1048576,
            storage_key="uploads/test_video.mp4",
            duration_seconds=30.5,
        )
        result = use_case.execute(command)

        assert result.id == "dup_123"
        assert result.user_id == "user_1"
        mock_repo.create.assert_called_once()
        created = mock_repo.create.call_args[0][0]
        assert isinstance(created, DuplicationRecord)
        assert created.status == "pending"

    def test_upload_default_duration(self, mock_repo):
        """不传 duration 默认 0.0"""
        mock_repo.create.side_effect = lambda x: x
        use_case = UploadForDuplicationUseCase(mock_repo)

        command = UploadForDuplicationCommand(
            user_id="user_1",
            filename="test.mp4",
            file_size=1000,
            storage_key="key",
        )
        result = use_case.execute(command)

        assert result.duration_seconds == 0.0

    def test_upload_empty_user_id_raises(self, mock_repo):
        """空 user_id 在 domain 层抛出"""
        use_case = UploadForDuplicationUseCase(mock_repo)

        command = UploadForDuplicationCommand(
            user_id="",
            filename="test.mp4",
            file_size=1000,
            storage_key="key",
        )
        with pytest.raises(ValueError, match="user_id cannot be empty"):
            use_case.execute(command)

        mock_repo.create.assert_not_called()

    def test_upload_zero_file_size_raises(self, mock_repo):
        """文件大小为0抛出"""
        use_case = UploadForDuplicationUseCase(mock_repo)

        command = UploadForDuplicationCommand(
            user_id="user_1",
            filename="test.mp4",
            file_size=0,
            storage_key="key",
        )
        with pytest.raises(ValueError, match="file_size must be positive"):
            use_case.execute(command)


class TestListDuplicationRecordsUseCase:
    """ListDuplicationRecordsUseCase 测试"""

    def test_list_returns_results(self, mock_repo, sample_record):
        """正常返回用户查重记录列表"""
        mock_repo.list_by_user.return_value = [sample_record]
        use_case = ListDuplicationRecordsUseCase(mock_repo)

        result = use_case.execute("user_1")

        assert len(result) == 1
        assert result[0].id == "dup_123"
        mock_repo.list_by_user.assert_called_once_with("user_1", offset=0, limit=50)

    def test_list_with_offset_limit(self, mock_repo, sample_record):
        """带 offset 和 limit 参数"""
        mock_repo.list_by_user.return_value = [sample_record]
        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_empty_user_id_raises(self, mock_repo):
        """空 user_id 抛出"""
        use_case = ListDuplicationRecordsUseCase(mock_repo)

        with pytest.raises(ValueError, match="user_id 不能为空"):
            use_case.execute("")

        mock_repo.list_by_user.assert_not_called()

    def test_whitespace_user_id_raises(self, mock_repo):
        """纯空格 user_id 抛出"""
        use_case = ListDuplicationRecordsUseCase(mock_repo)

        with pytest.raises(ValueError, match="user_id 不能为空"):
            use_case.execute("   ")

    def test_user_id_stripped(self, mock_repo, sample_record):
        """user_id 被 strip"""
        mock_repo.list_by_user.return_value = [sample_record]
        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:
    """GetDuplicationDetailUseCase 测试"""

    def test_get_existing(self, mock_repo, sample_record):
        """获取存在的记录"""
        mock_repo.get.return_value = sample_record
        use_case = GetDuplicationDetailUseCase(mock_repo)

        result = use_case.execute("dup_123")

        assert result is not None
        assert result.id == "dup_123"
        mock_repo.get.assert_called_once_with("dup_123")

    def test_get_nonexistent_returns_none(self, mock_repo):
        """获取不存在的记录返回 None"""
        mock_repo.get.return_value = None
        use_case = GetDuplicationDetailUseCase(mock_repo)

        result = use_case.execute("nonexistent")

        assert result is None


class TestDeleteDuplicationRecordUseCase:
    """DeleteDuplicationRecordUseCase 测试"""

    def test_delete_success(self, mock_repo):
        """删除成功"""
        mock_repo.delete.return_value = True
        use_case = DeleteDuplicationRecordUseCase(mock_repo)

        result = use_case.execute("dup_123")

        assert result is True
        mock_repo.delete.assert_called_once_with("dup_123")

    def test_delete_nonexistent_returns_false(self, mock_repo):
        """删除不存在的记录返回 False"""
        mock_repo.delete.return_value = False
        use_case = DeleteDuplicationRecordUseCase(mock_repo)

        result = use_case.execute("nonexistent")

        assert result is False


class TestRetryDuplicationUseCase:
    """RetryDuplicationUseCase 测试"""

    def test_retry_failed_record(self, mock_repo, failed_record):
        """失败记录可以重试，状态重置为 pending"""
        mock_repo.get.return_value = failed_record
        mock_repo.update.side_effect = lambda x: x
        use_case = RetryDuplicationUseCase(mock_repo)

        result = use_case.execute("dup_456")

        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_retry_nonexistent_returns_none(self, mock_repo):
        """重试不存在的记录返回 None"""
        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_retry_pending_raises(self, mock_repo, sample_record):
        """pending 状态的记录不能重试"""
        assert sample_record.status == "pending"
        mock_repo.get.return_value = sample_record
        use_case = RetryDuplicationUseCase(mock_repo)

        with pytest.raises(ValueError, match="只有 failed 状态的记录可以重试"):
            use_case.execute("dup_123")

        mock_repo.update.assert_not_called()

    def test_retry_completed_raises(self, mock_repo, sample_record):
        """completed 状态的记录不能重试"""
        sample_record.mark_completed(duplicate_rate=25.5, duplicate_count=3, segments=[])
        mock_repo.get.return_value = sample_record
        use_case = RetryDuplicationUseCase(mock_repo)

        with pytest.raises(ValueError, match="只有 failed 状态的记录可以重试"):
            use_case.execute("dup_123")

        mock_repo.update.assert_not_called()


class TestUploadForDuplicationCommand:
    """UploadForDuplicationCommand 数据类测试"""

    def test_command_fields(self):
        """命令对象字段正确"""
        cmd = UploadForDuplicationCommand(
            user_id="user_1",
            filename="test.mp4",
            file_size=1024,
            storage_key="uploads/test.mp4",
            duration_seconds=15.0,
        )
        assert cmd.user_id == "user_1"
        assert cmd.filename == "test.mp4"
        assert cmd.file_size == 1024
        assert cmd.storage_key == "uploads/test.mp4"
        assert cmd.duration_seconds == 15.0

    def test_default_duration(self):
        """duration_seconds 默认 0.0"""
        cmd = UploadForDuplicationCommand(
            user_id="user_1",
            filename="test.mp4",
            file_size=1024,
            storage_key="key",
        )
        assert cmd.duration_seconds == 0.0

    def test_command_is_dataclass(self):
        """是 dataclass"""
        from dataclasses import is_dataclass

        assert is_dataclass(UploadForDuplicationCommand)
