"""
标题库（Title Library）Use Case 回归测试

测试目标：
1. CreateTitleLibraryUseCase - 创建标题库条目
2. UpdateTitleLibraryUseCase - 更新标题库条目
3. 配额逻辑覆盖 - titles: free=50, basic=500, premium=500
4. 边界条件与异常场景
"""

from unittest.mock import Mock

import pytest

from packages.application.title_library.commands import (
    CreateTitleLibraryCommand,
    UpdateTitleLibraryCommand,
)
from packages.application.title_library.use_cases import (
    CreateTitleLibraryUseCase,
    DeleteTitleLibraryUseCase,
    GetTitleLibraryUseCase,
    ListTitleLibraryUseCase,
    NotFoundError,
    QuotaExceededError,
    UpdateTitleLibraryUseCase,
)
from packages.domain.title_library import TitleLibraryItem

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def mock_repo():
    """创建 Mock 仓储"""
    repo = Mock()
    repo.count_by_user = Mock(return_value=0)
    repo.create = Mock(side_effect=lambda item: item)
    repo.update = Mock(side_effect=lambda item: item)
    repo.get = Mock(return_value=None)
    repo.delete = Mock(return_value=True)
    repo.list_by_user = Mock(return_value=[])
    return repo


@pytest.fixture
def create_use_case(mock_repo):
    return CreateTitleLibraryUseCase(repository=mock_repo)


@pytest.fixture
def update_use_case(mock_repo):
    return UpdateTitleLibraryUseCase(repository=mock_repo)


@pytest.fixture
def sample_create_command():
    """标准创建命令"""
    return CreateTitleLibraryCommand(
        user_id="user-001",
        name="测试标题",
        text="这是一个测试标题文本",
        category="新闻",
        description="用于测试的标题",
        tags=["测试", "新闻"],
        metadata_={"source": "unit_test"},
    )


@pytest.fixture
def existing_title_item():
    """模拟已存在的标题条目"""
    return TitleLibraryItem(
        id="existing-title-001",
        user_id="user-001",
        name="旧标题",
        text="旧文本",
        category="旧分类",
        description="旧描述",
        tags=["旧"],
        is_active=True,
        metadata_={},
    )


# ===========================================================================
# 1. CreateTitleLibraryUseCase 测试
# ===========================================================================


class TestCreateTitleLibraryUseCase:
    """标题库创建 UseCase 测试"""

    def test_create_success_all_fields(self, create_use_case, mock_repo, sample_create_command):
        """测试创建成功 - 所有字段完整传入"""
        result = create_use_case.execute(sample_create_command, plan_name="free")

        assert result is not None
        assert result.user_id == "user-001"
        assert result.name == "测试标题"
        assert result.text == "这是一个测试标题文本"
        assert result.category == "新闻"
        assert result.description == "用于测试的标题"
        assert result.tags == ["测试", "新闻"]
        assert result.metadata_ == {"source": "unit_test"}

        mock_repo.count_by_user.assert_called_once_with("user-001")
        mock_repo.create.assert_called_once()

    def test_create_generates_uuid(self, create_use_case, mock_repo, sample_create_command):
        """测试创建时自动生成 UUID 作为 id"""
        result = create_use_case.execute(sample_create_command, plan_name="free")

        assert result.id is not None
        assert len(result.id) == 32  # uuid4().hex 长度为 32
        assert result.id.isalnum()

    def test_create_default_values(self, create_use_case, mock_repo):
        """测试默认值填充"""
        command = CreateTitleLibraryCommand(
            user_id="user-001",
            name="最小化创建",
            text="文本",
        )

        result = create_use_case.execute(command, plan_name="free")

        assert result.category == "default"
        assert result.description == ""
        assert result.tags == []
        assert result.metadata_ == {}


# ===========================================================================
# 2. 配额逻辑测试（titles: free=50, basic=500, premium=500）
# ===========================================================================


class TestCreateTitleLibraryQuota:
    """标题库创建配额检查测试"""

    def test_quota_free_plan_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """free 套餐（上限50），当前 25 个，允许创建"""
        mock_repo.count_by_user.return_value = 25

        result = create_use_case.execute(sample_create_command, plan_name="free")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_free_plan_at_limit(self, create_use_case, mock_repo, sample_create_command):
        """free 套餐（上限50），当前 50 个，拒绝创建"""
        mock_repo.count_by_user.return_value = 50

        with pytest.raises(QuotaExceededError) as exc_info:
            create_use_case.execute(sample_create_command, plan_name="free")

        assert exc_info.value.dimension == "max_titles"
        assert exc_info.value.limit == 50
        assert exc_info.value.used == 50
        mock_repo.create.assert_not_called()

    def test_quota_free_plan_just_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """free 套餐（上限50），当前 49 个，允许创建（边界）"""
        mock_repo.count_by_user.return_value = 49

        result = create_use_case.execute(sample_create_command, plan_name="free")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_free_plan_over_limit(self, create_use_case, mock_repo, sample_create_command):
        """free 套餐（上限50），当前 60 个，拒绝创建"""
        mock_repo.count_by_user.return_value = 60

        with pytest.raises(QuotaExceededError) as exc_info:
            create_use_case.execute(sample_create_command, plan_name="free")

        assert exc_info.value.dimension == "max_titles"
        assert exc_info.value.limit == 50
        assert exc_info.value.used == 60

    def test_quota_basic_plan_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """basic 套餐（上限500），当前 200 个，允许创建"""
        mock_repo.count_by_user.return_value = 200

        result = create_use_case.execute(sample_create_command, plan_name="basic")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_basic_plan_at_limit(self, create_use_case, mock_repo, sample_create_command):
        """basic 套餐（上限500），当前 500 个，拒绝创建"""
        mock_repo.count_by_user.return_value = 500

        with pytest.raises(QuotaExceededError) as exc_info:
            create_use_case.execute(sample_create_command, plan_name="basic")

        assert exc_info.value.dimension == "max_titles"
        assert exc_info.value.limit == 500
        assert exc_info.value.used == 500

    def test_quota_basic_plan_just_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """basic 套餐（上限500），当前 499 个，允许创建（边界）"""
        mock_repo.count_by_user.return_value = 499

        result = create_use_case.execute(sample_create_command, plan_name="basic")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_premium_plan_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """premium 套餐（上限500），当前 250 个，允许创建"""
        mock_repo.count_by_user.return_value = 250

        result = create_use_case.execute(sample_create_command, plan_name="premium")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_premium_plan_at_limit(self, create_use_case, mock_repo, sample_create_command):
        """premium 套餐（上限500），当前 500 个，拒绝创建"""
        mock_repo.count_by_user.return_value = 500

        with pytest.raises(QuotaExceededError) as exc_info:
            create_use_case.execute(sample_create_command, plan_name="premium")

        assert exc_info.value.dimension == "max_titles"
        assert exc_info.value.limit == 500
        assert exc_info.value.used == 500

    def test_quota_premium_plan_just_under_limit(self, create_use_case, mock_repo, sample_create_command):
        """premium 套餐（上限500），当前 499 个，允许创建（边界）"""
        mock_repo.count_by_user.return_value = 499

        result = create_use_case.execute(sample_create_command, plan_name="premium")

        assert result is not None
        mock_repo.create.assert_called_once()

    def test_quota_zero_usage_all_plans(self, create_use_case, mock_repo, sample_create_command):
        """新用户零使用量，所有套餐均可创建"""
        mock_repo.count_by_user.return_value = 0

        for plan in ["free", "basic", "premium"]:
            mock_repo.create.reset_mock()
            mock_repo.count_by_user.reset_mock()
            mock_repo.count_by_user.return_value = 0

            result = create_use_case.execute(sample_create_command, plan_name=plan)
            assert result is not None, f"{plan} 套餐零使用量应允许创建"

    def test_quota_unknown_plan_defaults_to_zero(self, create_use_case, mock_repo, sample_create_command):
        """未知套餐名默认配额为 0，无法创建"""
        mock_repo.count_by_user.return_value = 0

        with pytest.raises(QuotaExceededError):
            create_use_case.execute(sample_create_command, plan_name="unknown_plan")

    def test_quota_exceeded_error_attributes(self, create_use_case, mock_repo, sample_create_command):
        """QuotaExceededError 异常属性完整性"""
        mock_repo.count_by_user.return_value = 50

        with pytest.raises(QuotaExceededError) as exc_info:
            create_use_case.execute(sample_create_command, plan_name="free")

        err = exc_info.value
        assert hasattr(err, "dimension")
        assert hasattr(err, "limit")
        assert hasattr(err, "used")
        assert "max_titles" in str(err)
        assert "50" in str(err)


# ===========================================================================
# 3. UpdateTitleLibraryUseCase 测试
# ===========================================================================


class TestUpdateTitleLibraryUseCase:
    """标题库更新 UseCase 测试"""

    def test_update_success_all_fields(self, update_use_case, mock_repo, existing_title_item):
        """测试全字段更新成功"""
        mock_repo.get.return_value = existing_title_item

        command = UpdateTitleLibraryCommand(
            title_id="existing-title-001",
            user_id="user-001",
            name="更新后标题",
            text="更新后文本",
            category="新分类",
            description="新描述",
            tags=["新标签"],
            is_active=False,
            metadata_={"updated": True},
        )

        result = update_use_case.execute(command)

        assert result.name == "更新后标题"
        assert result.text == "更新后文本"
        assert result.category == "新分类"
        assert result.description == "新描述"
        assert result.tags == ["新标签"]
        assert result.is_active is False
        assert result.metadata_ == {"updated": True}

        mock_repo.update.assert_called_once()

    def test_update_partial_only_name(self, update_use_case, mock_repo, existing_title_item):
        """测试仅更新 name"""
        mock_repo.get.return_value = existing_title_item

        command = UpdateTitleLibraryCommand(
            title_id="existing-title-001",
            user_id="user-001",
            name="仅改名",
        )

        result = update_use_case.execute(command)

        assert result.name == "仅改名"
        # 其他字段保持不变
        assert result.text == "旧文本"
        assert result.category == "旧分类"
        assert result.description == "旧描述"

    def test_update_partial_only_is_active(self, update_use_case, mock_repo, existing_title_item):
        """测试仅更新 is_active（软删除/恢复）"""
        mock_repo.get.return_value = existing_title_item

        command = UpdateTitleLibraryCommand(
            title_id="existing-title-001",
            user_id="user-001",
            is_active=False,
        )

        result = update_use_case.execute(command)

        assert result.is_active is False
        assert result.name == "旧标题"  # 其他字段不变

    def test_update_not_found(self, update_use_case, mock_repo):
        """测试更新不存在的条目"""
        mock_repo.get.return_value = None

        command = UpdateTitleLibraryCommand(
            title_id="nonexistent-id",
            user_id="user-001",
            name="不存在",
        )

        with pytest.raises(NotFoundError, match="nonexistent-id"):
            update_use_case.execute(command)

        mock_repo.update.assert_not_called()

    def test_update_wrong_user(self, update_use_case, mock_repo):
        """测试用户隔离"""
        mock_repo.get.return_value = None

        command = UpdateTitleLibraryCommand(
            title_id="existing-title-001",
            user_id="other-user-999",
            name="恶意修改",
        )

        with pytest.raises(NotFoundError):
            update_use_case.execute(command)

    def test_update_none_fields_not_changed(self, update_use_case, mock_repo, existing_title_item):
        """测试 None 字段不覆盖原有值"""
        mock_repo.get.return_value = existing_title_item

        command = UpdateTitleLibraryCommand(
            title_id="existing-title-001",
            user_id="user-001",
        )

        result = update_use_case.execute(command)

        assert result.name == "旧标题"
        assert result.text == "旧文本"
        assert result.category == "旧分类"
        assert result.is_active is True


# ===========================================================================
# 4. DeleteTitleLibraryUseCase 测试
# ===========================================================================


class TestDeleteTitleLibraryUseCase:
    """标题库删除 UseCase 测试"""

    def test_delete_success(self, mock_repo):
        """测试删除成功"""
        mock_repo.delete.return_value = True
        use_case = DeleteTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("title-001", "user-001")

        assert result is True
        mock_repo.delete.assert_called_once_with("title-001", "user-001")

    def test_delete_not_found(self, mock_repo):
        """测试删除不存在的条目"""
        mock_repo.delete.return_value = False
        use_case = DeleteTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("nonexistent", "user-001")

        assert result is False


# ===========================================================================
# 5. GetTitleLibraryUseCase 测试
# ===========================================================================


class TestGetTitleLibraryUseCase:
    """标题库查询 UseCase 测试"""

    def test_get_existing(self, mock_repo):
        """测试查询存在的条目"""
        expected = TitleLibraryItem(
            id="t-001",
            user_id="user-001",
            name="测试",
            text="文本",
        )
        mock_repo.get.return_value = expected
        use_case = GetTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("t-001", "user-001")

        assert result is not None
        assert result.id == "t-001"
        mock_repo.get.assert_called_once_with("t-001", "user-001")

    def test_get_not_found(self, mock_repo):
        """测试查询不存在的条目"""
        mock_repo.get.return_value = None
        use_case = GetTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("nonexistent", "user-001")

        assert result is None


# ===========================================================================
# 6. ListTitleLibraryUseCase 测试
# ===========================================================================


class TestListTitleLibraryUseCase:
    """标题库列表 UseCase 测试"""

    def test_list_default(self, mock_repo):
        """测试默认列表查询"""
        items = [
            TitleLibraryItem(id="t1", user_id="user-001", name="A", text="a"),
            TitleLibraryItem(id="t2", user_id="user-001", name="B", text="b"),
        ]
        mock_repo.list_by_user.return_value = items
        use_case = ListTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("user-001")

        assert len(result) == 2
        mock_repo.list_by_user.assert_called_once_with("user-001", category=None, skip=0, limit=50)

    def test_list_with_category_filter(self, mock_repo):
        """测试按分类筛选"""
        mock_repo.list_by_user.return_value = []
        use_case = ListTitleLibraryUseCase(repository=mock_repo)

        use_case.execute("user-001", category="新闻", skip=5, limit=10)

        mock_repo.list_by_user.assert_called_once_with("user-001", category="新闻", skip=5, limit=10)

    def test_list_empty(self, mock_repo):
        """测试空列表"""
        mock_repo.list_by_user.return_value = []
        use_case = ListTitleLibraryUseCase(repository=mock_repo)

        result = use_case.execute("user-001")

        assert result == []
