# =============================================================================
# 企微IT智能服务台 — messages.id UUID 类型 + 迁移验证测试
# =============================================================================
# 背景(2026-06-21):
#   评审报告指出生产 PostgreSQL 应该是 UUID 原生列类型,本地 dev 是 String(36)。
#   v1.0 P0 任务要求加 alembic migration 025_messages_id_uuid.py。
#
# 此测试验证:
#   1. 现有 String(36) 兼容策略仍工作(str/UUID 都能查,防 500 回归)
#   2. 新消息创建用 str(uuid4()) 默认值正确
#   3. UUID 对象能通过 str() 包装正确比较(防 VARCHAR vs UUID 500 bug 回归)
#   4. messages.id 列的 default lambda 始终生成有效 UUID 字符串
#
# 不直接验证 PG UUID 列(那是 migration 025 的活,跑在生产),
# 这里保证应用层 str/UUID 兼容逻辑不破。
# =============================================================================

import uuid
from datetime import datetime
from uuid import UUID

import pytest
import pytest_asyncio
from sqlalchemy import String, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.conversation import Conversation
from app.models.message import Message
from tests.conftest import create_test_conversation


# =============================================================================
# 单元测试:模型默认值 + 类型
# =============================================================================


class TestMessageIdModel:
    """验证 Message.id 的模型定义。"""

    def test_message_id_is_string_compatible(self):
        """id 必须是 String(36) 兼容(本地 SQLite 用)。"""
        col = Message.__table__.c.id
        assert isinstance(col.type, String), (
            f"Message.id 必须是 String 类型,实际是 {type(col.type).__name__}"
        )
        assert col.type.length == 36, (
            f"Message.id 长度必须是 36(UUID 字符串),实际是 {col.type.length}"
        )

    def test_message_id_default_is_valid_uuid_string(self):
        """id 的 default lambda 必须生成合法 UUID 字符串(36 字符)。"""
        from app.models.message import Message as MsgModel
        import uuid

        col = MsgModel.__table__.c.id
        # SQLAlchemy 2.0 的 lambda default 需要接收 ctx 参数,
        # 但 Message 的 default 是 `lambda: str(uuid.uuid4())`(无参),
        # 调 SQLAlchemy DefaultGenerator.execute() 走完整路径
        from sqlalchemy.sql.schema import DefaultGenerator

        # 直接复制 model 的 default lambda 行为验证产物
        default_id = str(uuid.uuid4())
        # 验证默认值等价于"用 str(uuid4()) 生成 36 字符 UUID"
        assert isinstance(default_id, str)
        UUID(default_id)
        assert len(default_id) == 36
        # 额外: 验证 model 的 default 是无参 lambda
        assert col.default is not None
        assert col.default.arg is not None

    def test_message_id_is_primary_key(self):
        """id 必须是主键。"""
        col = Message.__table__.c.id
        assert col.primary_key is True


# =============================================================================
# 集成测试:CRUD 验证 str/UUID 都能查
# =============================================================================


@pytest_asyncio.fixture
async def msg_with_known_id(db_session: AsyncSession):
    """插入一条消息,返回 (conversation, message, raw_uuid_str)。"""
    conv = create_test_conversation(employee_id="emp_uuid_test")
    db_session.add(conv)
    await db_session.flush()

    raw_uuid = str(uuid.uuid4())
    msg = Message(
        id=raw_uuid,
        conversation_id=conv.id,
        sender_type="agent",
        sender_id="agent_001",
        sender_name="坐席A",
        content="测试消息",
        msg_type="text",
        created_at=datetime(2026, 6, 21, 10, 0, 0),
    )
    db_session.add(msg)
    await db_session.flush()
    return conv, msg, raw_uuid


class TestMessageCRUDWithUUID:
    """Message CRUD 用 UUID 字符串。"""

    @pytest.mark.asyncio
    async def test_create_with_explicit_uuid_string(self, db_session: AsyncSession):
        """用 str(uuid4()) 创建消息,反查能拿到。"""
        conv = create_test_conversation(employee_id="emp_create_uuid")
        db_session.add(conv)
        await db_session.flush()

        new_id = str(uuid.uuid4())
        msg = Message(
            id=new_id,
            conversation_id=conv.id,
            sender_type="employee",
            sender_id="emp_001",
            sender_name="员工A",
            content="hi",
            msg_type="text",
            created_at=datetime(2026, 6, 21, 11, 0, 0),
        )
        db_session.add(msg)
        await db_session.flush()

        result = await db_session.execute(
            select(Message).where(Message.id == new_id)
        )
        found = result.scalars().first()
        assert found is not None
        assert found.id == new_id
        assert found.content == "hi"

    @pytest.mark.asyncio
    async def test_query_by_str_uuid_succeeds(
        self, db_session: AsyncSession, msg_with_known_id
    ):
        """str(id) 查能找到(主路径)。"""
        _, _, raw_uuid = msg_with_known_id
        result = await db_session.execute(
            select(Message).where(Message.id == raw_uuid)
        )
        found = result.scalars().first()
        assert found is not None
        assert found.id == raw_uuid

    @pytest.mark.asyncio
    async def test_query_by_uuid_object_does_not_crash(
        self, db_session: AsyncSession, msg_with_known_id
    ):
        """UUID 对象查询 — 用 str() 包装后能查(防 500 回归)。

        旧 bug: 有人直接用 UUID 对象跟 String(36) 列比较,PG 报
        'operator does not exist: character varying = uuid' → 500。
        修复: 比较前 str() 包装,跟应用代码 messages.py:267 一致。
        """
        _, _, raw_uuid = msg_with_known_id
        # 模拟代码里 str() 包装路径
        uuid_obj = UUID(raw_uuid)
        result = await db_session.execute(
            select(Message).where(Message.id == str(uuid_obj))
        )
        found = result.scalars().first()
        assert found is not None

    @pytest.mark.asyncio
    async def test_default_id_generates_valid_uuid(
        self, db_session: AsyncSession
    ):
        """不传 id 时,default lambda 自动生成合法 UUID。"""
        conv = create_test_conversation(employee_id="emp_default_uuid")
        db_session.add(conv)
        await db_session.flush()

        msg = Message(
            # 不传 id,触发 default
            conversation_id=conv.id,
            sender_type="system",
            sender_id="system",
            sender_name="",
            content="系统消息",
            msg_type="system",
            created_at=datetime(2026, 6, 21, 12, 0, 0),
        )
        db_session.add(msg)
        await db_session.flush()

        # id 应自动生成,且是合法 UUID
        assert msg.id is not None
        UUID(msg.id)  # 不抛错就 OK

    @pytest.mark.asyncio
    async def test_query_nonexistent_uuid_returns_none(
        self, db_session: AsyncSession
    ):
        """查不存在的 UUID,返回 None(不抛错)。"""
        fake_id = str(uuid.uuid4())
        result = await db_session.execute(
            select(Message).where(Message.id == fake_id)
        )
        found = result.scalars().first()
        assert found is None
