"""Voice Clone Use Cases 单元测试"""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from packages.application.voice_clone.use_cases import (
    CreateVoiceCloneUseCase,
    DeleteVoiceCloneUseCase,
    GetVoiceCloneStatusUseCase,
    GetVoiceCloneUseCase,
    ListVoiceClonesUseCase,
    RetryVoiceCloneUseCase,
    VoiceCloneNotFoundError,
    VoiceCloneNotRetryableError,
)
from packages.domain.voice_clone_profile import VoiceCloneProfile, VoiceCloneStatus


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


@pytest.fixture
def sample_profile():
    return VoiceCloneProfile.create(
        user_id="user_001",
        name="我的音色",
        description="测试音色克隆",
        source_audio_url="https://example.com/audio.wav",
        voice_model="cosyvoice",
        language="zh-CN",
        gender="female",
        max_retries=3,
    )


class TestCreateVoiceCloneUseCase:
    """创建音色克隆用例测试"""

    def test_create_success(self, mock_repo, sample_profile):
        """创建成功"""
        mock_repo.create.return_value = sample_profile

        use_case = CreateVoiceCloneUseCase(mock_repo)
        result = use_case.execute(
            user_id="user_001",
            name="我的音色",
            source_audio_url="https://example.com/audio.wav",
        )

        assert result is not None
        assert result.user_id == "user_001"
        assert result.name == "我的音色"
        assert result.status == VoiceCloneStatus.PENDING
        mock_repo.create.assert_called_once()

    def test_create_with_default_params(self, mock_repo):
        """使用默认参数创建"""
        mock_repo.create.side_effect = lambda x: x

        use_case = CreateVoiceCloneUseCase(mock_repo)
        result = use_case.execute(user_id="user_001", name="测试音色")

        assert result.description == ""
        assert result.source_audio_url == ""
        assert result.voice_model == ""
        assert result.language == "zh-CN"
        assert result.gender == "unknown"
        assert result.max_retries == 3

    def test_create_with_metadata(self, mock_repo):
        """创建时携带 metadata"""
        mock_repo.create.side_effect = lambda x: x

        use_case = CreateVoiceCloneUseCase(mock_repo)
        result = use_case.execute(
            user_id="user_001",
            name="test",
            metadata={"source": "upload", "duration": 10},
        )

        assert result.metadata["source"] == "upload"
        assert result.metadata["duration"] == 10

    def test_create_custom_max_retries(self, mock_repo):
        """自定义重试次数"""
        mock_repo.create.side_effect = lambda x: x

        use_case = CreateVoiceCloneUseCase(mock_repo)
        result = use_case.execute(user_id="user_001", name="test", max_retries=5)

        assert result.max_retries == 5


class TestListVoiceClonesUseCase:
    """列出音色克隆用例测试"""

    def test_list_success(self, mock_repo, sample_profile):
        """列出成功"""
        mock_repo.list_by_user.return_value = [sample_profile]
        mock_repo.count_by_user.return_value = 1

        use_case = ListVoiceClonesUseCase(mock_repo)
        items, total = use_case.execute(user_id="user_001")

        assert len(items) == 1
        assert total == 1
        mock_repo.list_by_user.assert_called_once_with("user_001", status=None, limit=50, offset=0)

    def test_list_with_status_filter(self, mock_repo):
        """按状态过滤"""
        mock_repo.list_by_user.return_value = []
        mock_repo.count_by_user.return_value = 0

        use_case = ListVoiceClonesUseCase(mock_repo)
        items, total = use_case.execute(user_id="user_001", status="completed")

        assert total == 0
        mock_repo.list_by_user.assert_called_once_with("user_001", status="completed", limit=50, offset=0)

    def test_list_with_pagination(self, mock_repo):
        """分页参数正确传递"""
        mock_repo.list_by_user.return_value = []
        mock_repo.count_by_user.return_value = 0

        use_case = ListVoiceClonesUseCase(mock_repo)
        use_case.execute(user_id="user_001", skip=20, limit=10)

        mock_repo.list_by_user.assert_called_once_with("user_001", status=None, limit=10, offset=20)

    def test_list_empty(self, mock_repo):
        """空列表"""
        mock_repo.list_by_user.return_value = []
        mock_repo.count_by_user.return_value = 0

        use_case = ListVoiceClonesUseCase(mock_repo)
        items, total = use_case.execute(user_id="user_001")

        assert items == []
        assert total == 0


class TestGetVoiceCloneUseCase:
    """获取音色克隆详情用例测试"""

    def test_get_success(self, mock_repo, sample_profile):
        """获取成功"""
        mock_repo.get.return_value = sample_profile

        use_case = GetVoiceCloneUseCase(mock_repo)
        result = use_case.execute(clone_id=sample_profile.id, user_id="user_001")

        assert result.id == sample_profile.id
        mock_repo.get.assert_called_once_with(sample_profile.id)

    def test_get_not_found(self, mock_repo):
        """不存在抛异常"""
        mock_repo.get.return_value = None

        use_case = GetVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id="nonexistent", user_id="user_001")

    def test_get_wrong_user(self, mock_repo, sample_profile):
        """用户不匹配抛异常"""
        mock_repo.get.return_value = sample_profile

        use_case = GetVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id=sample_profile.id, user_id="other_user")


class TestGetVoiceCloneStatusUseCase:
    """查询音色克隆状态用例测试"""

    def test_get_status_success(self, mock_repo, sample_profile):
        """获取状态成功"""
        mock_repo.get.return_value = sample_profile

        use_case = GetVoiceCloneStatusUseCase(mock_repo)
        result = use_case.execute(clone_id=sample_profile.id, user_id="user_001")

        assert result.status == VoiceCloneStatus.PENDING

    def test_get_status_not_found(self, mock_repo):
        """不存在抛异常"""
        mock_repo.get.return_value = None

        use_case = GetVoiceCloneStatusUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id="nonexistent", user_id="user_001")

    def test_get_status_wrong_user(self, mock_repo, sample_profile):
        """用户不匹配抛异常"""
        mock_repo.get.return_value = sample_profile

        use_case = GetVoiceCloneStatusUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id=sample_profile.id, user_id="other_user")


class TestDeleteVoiceCloneUseCase:
    """删除音色克隆用例测试"""

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

        use_case = DeleteVoiceCloneUseCase(mock_repo)
        result = use_case.execute(clone_id=sample_profile.id, user_id="user_001")

        assert result is True
        mock_repo.delete.assert_called_once_with(sample_profile.id)

    def test_delete_not_found(self, mock_repo):
        """不存在返回 False"""
        mock_repo.get.return_value = None

        use_case = DeleteVoiceCloneUseCase(mock_repo)
        result = use_case.execute(clone_id="nonexistent", user_id="user_001")

        assert result is False
        mock_repo.delete.assert_not_called()

    def test_delete_wrong_user(self, mock_repo, sample_profile):
        """用户不匹配返回 False"""
        mock_repo.get.return_value = sample_profile

        use_case = DeleteVoiceCloneUseCase(mock_repo)
        result = use_case.execute(clone_id=sample_profile.id, user_id="other_user")

        assert result is False
        mock_repo.delete.assert_not_called()


class TestRetryVoiceCloneUseCase:
    """重试音色克隆用例测试"""

    def test_retry_success(self, mock_repo, sample_profile):
        """失败状态重试成功"""
        sample_profile.status = VoiceCloneStatus.FAILED
        sample_profile.retry_count = 1
        mock_repo.get.return_value = sample_profile
        mock_repo.update.side_effect = lambda x: x

        use_case = RetryVoiceCloneUseCase(mock_repo)
        result = use_case.execute(clone_id=sample_profile.id, user_id="user_001")

        assert result.status == VoiceCloneStatus.PENDING
        assert result.retry_count == 2
        mock_repo.update.assert_called_once()

    def test_retry_not_found(self, mock_repo):
        """不存在抛异常"""
        mock_repo.get.return_value = None

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id="nonexistent", user_id="user_001")

    def test_retry_wrong_user(self, mock_repo, sample_profile):
        """用户不匹配抛异常"""
        sample_profile.status = VoiceCloneStatus.FAILED
        mock_repo.get.return_value = sample_profile

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotFoundError):
            use_case.execute(clone_id=sample_profile.id, user_id="other_user")

    def test_retry_not_retryable_pending(self, mock_repo, sample_profile):
        """pending 状态不可重试"""
        sample_profile.status = VoiceCloneStatus.PENDING
        mock_repo.get.return_value = sample_profile

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotRetryableError):
            use_case.execute(clone_id=sample_profile.id, user_id="user_001")

    def test_retry_not_retryable_processing(self, mock_repo, sample_profile):
        """processing 状态不可重试"""
        sample_profile.status = VoiceCloneStatus.PROCESSING
        mock_repo.get.return_value = sample_profile

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotRetryableError):
            use_case.execute(clone_id=sample_profile.id, user_id="user_001")

    def test_retry_not_retryable_ready(self, mock_repo, sample_profile):
        """ready 状态不可重试"""
        sample_profile.status = VoiceCloneStatus.READY
        mock_repo.get.return_value = sample_profile

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotRetryableError):
            use_case.execute(clone_id=sample_profile.id, user_id="user_001")

    def test_retry_max_retries_exceeded(self, mock_repo, sample_profile):
        """超过重试上限不可重试"""
        sample_profile.status = VoiceCloneStatus.FAILED
        sample_profile.retry_count = 3
        sample_profile.max_retries = 3
        mock_repo.get.return_value = sample_profile

        use_case = RetryVoiceCloneUseCase(mock_repo)

        with pytest.raises(VoiceCloneNotRetryableError):
            use_case.execute(clone_id=sample_profile.id, user_id="user_001")
