"""封面模板 CRUD 单元测试。

验证：
1. 领域实体：创建系统/用户模板、更新、空名称校验
2. 仓储接口：list_for_user 返回系统+用户模板
3. API 路由：CRUD 权限检查（系统模板不可删/改）
4. Schema：请求/响应序列化
"""

from datetime import datetime, timezone
from unittest.mock import MagicMock, patch

import pytest

from packages.domain.cover_template import CoverTemplate


class TestCoverTemplateDomain:
    """测试封面模板领域实体。"""

    def test_create_system_template(self):
        """测试创建系统模板。"""
        tpl = CoverTemplate.create_system(name="默认模板")
        assert tpl.is_system is True
        assert tpl.user_id is None
        assert tpl.name == "默认模板"
        assert tpl.config == {}

    def test_create_system_template_with_config(self):
        """测试创建带配置的系统模板。"""
        config = {"background_color": "#ef4444", "title_text": "Hello"}
        tpl = CoverTemplate.create_system(name="大胆红", config=config)
        assert tpl.config == config
        assert tpl.name == "大胆红"

    def test_create_user_template(self):
        """测试创建用户自定义模板。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="我的模板")
        assert tpl.is_system is False
        assert tpl.user_id == "user-1"
        assert tpl.name == "我的模板"

    def test_create_user_template_strips_whitespace(self):
        """测试创建用户模板时自动去除首尾空格。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="  我的模板  ")
        assert tpl.name == "我的模板"

    def test_create_user_template_empty_name_raises(self):
        """测试空名称抛出 ValueError。"""
        with pytest.raises(ValueError, match="模板名称不能为空"):
            CoverTemplate.create_user(user_id="user-1", name="")

        with pytest.raises(ValueError, match="模板名称不能为空"):
            CoverTemplate.create_user(user_id="user-1", name="   ")

    def test_update_name(self):
        """测试更新模板名称。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="旧名称")
        old_updated_at = tpl.updated_at
        tpl.update(name="新名称")
        assert tpl.name == "新名称"
        assert tpl.updated_at >= old_updated_at

    def test_update_config(self):
        """测试更新模板配置。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        new_config = {"background_color": "#000", "mask_enabled": True}
        tpl.update(config=new_config)
        assert tpl.config == new_config

    def test_update_thumbnail_url(self):
        """测试更新缩略图 URL。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        tpl.update(thumbnail_url="https://example.com/thumb.jpg")
        assert tpl.thumbnail_url == "https://example.com/thumb.jpg"

    def test_update_empty_name_raises(self):
        """测试更新空名称抛出 ValueError。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        with pytest.raises(ValueError, match="模板名称不能为空"):
            tpl.update(name="")


class TestCoverTemplateRepository:
    """测试封面模板仓储（使用 Mock）。"""

    @pytest.fixture
    def mock_repo(self):
        """创建模拟仓储。"""
        from packages.adapters.sqlalchemy_impl.cover_template_repository import (
            SQLAlchemyCoverTemplateRepository,
        )

        mock_session = MagicMock()
        return SQLAlchemyCoverTemplateRepository(mock_session)

    def test_create_calls_session_add(self, mock_repo):
        """测试 create 方法调用 session.add。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        mock_repo.create(tpl)
        mock_repo.session.add.assert_called_once()
        mock_repo.session.commit.assert_called_once()

    def test_get_returns_none_for_nonexistent(self, mock_repo):
        """测试 get 方法对不存在的模板返回 None。"""
        mock_repo.session.query.return_value.filter.return_value.first.return_value = None
        result = mock_repo.get("nonexistent-id")
        assert result is None


class TestCoverTemplateSchema:
    """测试封面模板 Schema。"""

    def test_create_request_validation(self):
        """测试创建请求的字段验证。"""
        from app.schemas.cover_template import CreateCoverTemplateRequest

        req = CreateCoverTemplateRequest(name="测试模板")
        assert req.name == "测试模板"
        assert req.thumbnail_url == ""
        assert req.config is None

    def test_create_request_with_config(self):
        """测试带配置的创建请求。"""
        from app.schemas.cover_template import CoverTemplateConfig, CreateCoverTemplateRequest

        config = CoverTemplateConfig(
            background_enabled=False,
            background_color="#ff0000",
            title_text="主标题",
        )
        req = CreateCoverTemplateRequest(name="测试", config=config)
        assert req.config.background_enabled is False
        assert req.config.background_color == "#ff0000"
        assert req.config.title_text == "主标题"

    def test_update_request_optional_fields(self):
        """测试更新请求所有字段可选。"""
        from app.schemas.cover_template import UpdateCoverTemplateRequest

        req = UpdateCoverTemplateRequest()
        assert req.name is None
        assert req.config is None
        assert req.thumbnail_url is None

    def test_response_serialization(self):
        """测试响应序列化。"""
        from app.schemas.cover_template import CoverTemplateResponse

        now = datetime.now(timezone.utc)
        resp = CoverTemplateResponse(
            id="test-id",
            name="测试",
            thumbnail_url="",
            is_system=False,
            created_at=now,
            config={"background_color": "#000"},
        )
        assert resp.id == "test-id"
        assert resp.config["background_color"] == "#000"


class TestCoverTemplateAPIPermissions:
    """测试封面模板 API 权限控制。"""

    def test_system_template_cannot_be_deleted(self):
        """测试系统模板不可删除。"""
        tpl = CoverTemplate.create_system(name="系统模板")
        mock_repo = MagicMock()
        mock_repo.get.return_value = tpl

        # 模拟 API 路由中的权限检查逻辑
        template = mock_repo.get("a8b0120fd98e44788f5a6590f983d327")
        assert template is not None
        assert template.is_system is True
        # 权限检查应该阻止删除
        with pytest.raises(PermissionError):
            if template.is_system:
                raise PermissionError("系统模板不可删除")

    def test_system_template_cannot_be_updated(self):
        """测试系统模板不可修改。"""
        tpl = CoverTemplate.create_system(name="系统模板")
        mock_repo = MagicMock()
        mock_repo.get.return_value = tpl

        template = mock_repo.get("a8b0120fd98e44788f5a6590f983d327")
        assert template.is_system is True
        with pytest.raises(PermissionError):
            if template.is_system:
                raise PermissionError("系统模板不可修改")

    def test_user_cannot_delete_others_template(self):
        """测试用户不可删除他人的模板。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="别人的模板")
        mock_repo = MagicMock()
        mock_repo.get.return_value = tpl

        template = mock_repo.get(tpl.id)
        current_user_id = "user-2"
        assert template.user_id != current_user_id
        with pytest.raises(PermissionError):
            if template.user_id != current_user_id:
                raise PermissionError("无权删除该模板")

    def test_user_can_delete_own_template(self):
        """测试用户可以删除自己的模板。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="我的模板")
        mock_repo = MagicMock()
        mock_repo.get.return_value = tpl

        template = mock_repo.get(tpl.id)
        current_user_id = "user-1"
        assert template.user_id == current_user_id
        assert not template.is_system
        # 权限检查通过，可以删除
        mock_repo.delete(template.id)
        mock_repo.delete.assert_called_once()


class TestInMemoryCoverTemplateRepository:
    """使用 InMemory 仓储测试完整 CRUD 流程。"""

    @pytest.fixture
    def repo(self):
        from packages.adapters.in_memory.cover_template_repository import (
            InMemoryCoverTemplateRepository,
        )

        return InMemoryCoverTemplateRepository()

    def test_create_and_get(self, repo):
        """创建后能查到。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        repo.create(tpl)
        found = repo.get(tpl.id)
        assert found is not None
        assert found.name == "测试"
        assert found.user_id == "user-1"

    def test_get_nonexistent_returns_none(self, repo):
        """查不到返回 None。"""
        assert repo.get("nonexistent") is None

    def test_list_for_user_includes_system_and_own(self, repo):
        """list_for_user 返回系统模板 + 用户自己的模板。"""
        sys_tpl = CoverTemplate.create_system(name="系统模板")
        repo.create(sys_tpl)

        user1_tpl = CoverTemplate.create_user(user_id="user-1", name="用户1的")
        repo.create(user1_tpl)

        user2_tpl = CoverTemplate.create_user(user_id="user-2", name="用户2的")
        repo.create(user2_tpl)

        # user-1 应该看到系统模板 + 自己的
        user1_visible = repo.list_for_user("user-1")
        assert len(user1_visible) == 2
        names = {t.name for t in user1_visible}
        assert "系统模板" in names
        assert "用户1的" in names
        assert "用户2的" not in names

    def test_count_for_user(self, repo):
        """count_for_user 返回正确的数量。"""
        repo.create(CoverTemplate.create_system(name="系统1"))
        repo.create(CoverTemplate.create_system(name="系统2"))
        repo.create(CoverTemplate.create_user(user_id="user-1", name="用户1的"))

        assert repo.count_for_user("user-1") == 3
        assert repo.count_for_user("user-2") == 2  # 只能看到2个系统模板

    def test_update_template(self, repo):
        """更新模板。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="旧名称")
        repo.create(tpl)
        tpl.update(name="新名称", config={"background_color": "#ff0000"})
        repo.update(tpl)

        found = repo.get(tpl.id)
        assert found.name == "新名称"
        assert found.config["background_color"] == "#ff0000"

    def test_update_nonexistent_raises(self, repo):
        """更新不存在的模板抛出异常。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        with pytest.raises(ValueError, match="不存在"):
            repo.update(tpl)

    def test_delete_template(self, repo):
        """删除模板。"""
        tpl = CoverTemplate.create_user(user_id="user-1", name="测试")
        repo.create(tpl)
        assert repo.delete(tpl.id) is True
        assert repo.get(tpl.id) is None

    def test_delete_nonexistent_returns_false(self, repo):
        """删除不存在的模板返回 False。"""
        assert repo.delete("nonexistent") is False

    def test_list_system_templates(self, repo):
        """list_system_templates 只返回系统模板。"""
        repo.create(CoverTemplate.create_system(name="系统1"))
        repo.create(CoverTemplate.create_system(name="系统2"))
        repo.create(CoverTemplate.create_user(user_id="user-1", name="用户1的"))

        system = repo.list_system_templates()
        assert len(system) == 2
        assert all(t.is_system for t in system)


class TestCoverTemplatesErrorHandling:
    """测试 API 错误处理加固 - 数据库表不存在时降级处理。"""

    def test_list_raises_operational_error_when_table_missing(self):
        """当 cover_templates 表不存在时，repository 应抛出 OperationalError。"""
        from sqlalchemy.exc import OperationalError

        from packages.adapters.sqlalchemy_impl.cover_template_repository import SQLAlchemyCoverTemplateRepository

        mock_session = MagicMock()
        mock_session.query.side_effect = OperationalError(
            "statement", {}, Exception('relation "cover_templates" does not exist')
        )
        repo = SQLAlchemyCoverTemplateRepository(mock_session)

        with pytest.raises(OperationalError):
            repo.list_for_user("user-1")

    def test_route_catches_operational_error_on_list(self):
        """路由层应捕获 OperationalError 并返回空列表。"""
        from app.api.routes.cover_templates import list_cover_templates
        from app.schemas.cover_template import ListCoverTemplatesResponse
        from sqlalchemy.exc import OperationalError

        mock_repo = MagicMock()
        mock_repo.list_for_user.side_effect = OperationalError(
            "statement", {}, Exception('relation "cover_templates" does not exist')
        )

        mock_user = MagicMock()
        mock_user.user.id = "user-1"

        # 调用路由函数，验证它不会抛出异常
        result = list_cover_templates(
            skip=0,
            limit=100,
            authenticated_user=mock_user,
            repo=mock_repo,
        )
        assert isinstance(result, ListCoverTemplatesResponse)
        assert result.items == []
        assert result.total == 0

    def test_route_catches_programming_error_on_list(self):
        """路由层应捕获 ProgrammingError 并返回空列表。"""
        from app.api.routes.cover_templates import list_cover_templates
        from app.schemas.cover_template import ListCoverTemplatesResponse
        from sqlalchemy.exc import ProgrammingError

        mock_repo = MagicMock()
        mock_repo.list_for_user.side_effect = ProgrammingError(
            "statement", {}, Exception("no such table: cover_templates")
        )

        mock_user = MagicMock()
        mock_user.user.id = "user-1"

        result = list_cover_templates(
            skip=0,
            limit=100,
            authenticated_user=mock_user,
            repo=mock_repo,
        )
        assert isinstance(result, ListCoverTemplatesResponse)
        assert result.items == []
        assert result.total == 0

    def test_create_returns_503_when_table_missing(self):
        """创建模板时，如果表不存在应抛出 HTTPException(503)。"""
        from app.api.routes.cover_templates import create_cover_template
        from app.schemas.cover_template import CreateCoverTemplateRequest
        from fastapi import HTTPException
        from sqlalchemy.exc import OperationalError

        mock_repo = MagicMock()
        mock_repo.create.side_effect = OperationalError(
            "statement", {}, Exception('relation "cover_templates" does not exist')
        )

        mock_user = MagicMock()
        mock_user.user.id = "user-1"

        request = CreateCoverTemplateRequest(name="test")

        with pytest.raises(HTTPException) as exc_info:
            create_cover_template(
                request=request,
                authenticated_user=mock_user,
                repo=mock_repo,
            )
        assert exc_info.value.status_code == 503
