"""
Unit tests for SlotDefinitionService.
[AC-MRS-07,08,16] 验证槽位定义管理功能
"""

import uuid
import pytest
from unittest.mock import AsyncMock, MagicMock
from sqlalchemy.ext.asyncio import AsyncSession

from app.services.slot_definition_service import SlotDefinitionService
from app.models.entities import (
    SlotDefinition,
    SlotDefinitionCreate,
    SlotDefinitionUpdate,
    MetadataFieldDefinition,
)


class TestSlotDefinitionService:
    """[AC-MRS-07,08,16] SlotDefinitionService 测试"""

    @pytest.fixture
    def mock_session(self):
        """Mock AsyncSession"""
        session = MagicMock(spec=AsyncSession)
        session.execute = AsyncMock()
        session.add = MagicMock()
        session.flush = AsyncMock()
        session.delete = AsyncMock()
        return session

    @pytest.fixture
    def service(self, mock_session):
        """Create service instance"""
        return SlotDefinitionService(mock_session)

    @pytest.mark.asyncio
    async def test_list_slot_definitions(self, service, mock_session):
        """列出槽位定义"""
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.id = uuid.uuid4()
        mock_slot.slot_key = "grade"
        
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [mock_slot]
        mock_session.execute.return_value = mock_result
        
        slots = await service.list_slot_definitions("test-tenant")
        
        assert len(slots) == 1
        assert slots[0].slot_key == "grade"

    @pytest.mark.asyncio
    async def test_list_slot_definitions_filter_required(self, service, mock_session):
        """按必填过滤槽位定义"""
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.required = True
        
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [mock_slot]
        mock_session.execute.return_value = mock_result
        
        slots = await service.list_slot_definitions("test-tenant", required=True)
        
        assert len(slots) == 1
        assert slots[0].required is True

    @pytest.mark.asyncio
    async def test_get_slot_definition(self, service, mock_session):
        """获取单个槽位定义"""
        slot_id = uuid.uuid4()
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.id = slot_id
        mock_slot.slot_key = "grade"
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_slot
        mock_session.execute.return_value = mock_result
        
        slot = await service.get_slot_definition("test-tenant", str(slot_id))
        
        assert slot is not None
        assert slot.slot_key == "grade"

    @pytest.mark.asyncio
    async def test_get_slot_definition_not_found(self, service, mock_session):
        """获取不存在的槽位定义"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        slot = await service.get_slot_definition("test-tenant", str(uuid.uuid4()))
        
        assert slot is None

    @pytest.mark.asyncio
    async def test_get_slot_definition_by_key(self, service, mock_session):
        """通过 slot_key 获取槽位定义"""
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.slot_key = "grade"
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_slot
        mock_session.execute.return_value = mock_result
        
        slot = await service.get_slot_definition_by_key("test-tenant", "grade")
        
        assert slot is not None
        assert slot.slot_key == "grade"

    @pytest.mark.asyncio
    async def test_create_slot_definition(self, service, mock_session):
        """[AC-MRS-07] 创建槽位定义"""
        slot_create = SlotDefinitionCreate(
            slot_key="grade",
            type="string",
            required=True,
            extract_strategy="llm",
            ask_back_prompt="请输入年级",
        )
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        slot = await service.create_slot_definition("test-tenant", slot_create)
        
        assert slot is not None
        mock_session.add.assert_called_once()
        mock_session.flush.assert_called_once()

    @pytest.mark.asyncio
    async def test_create_slot_definition_invalid_key(self, service):
        """[AC-MRS-07] 创建无效 slot_key 抛出异常"""
        slot_create = SlotDefinitionCreate(
            slot_key="InvalidKey",
            type="string",
            required=True,
        )
        
        with pytest.raises(ValueError) as exc_info:
            await service.create_slot_definition("test-tenant", slot_create)
        
        assert "格式不正确" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_create_slot_definition_duplicate_key(self, service, mock_session):
        """[AC-MRS-07] 创建重复 slot_key 抛出异常"""
        existing_slot = MagicMock(spec=SlotDefinition)
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = existing_slot
        mock_session.execute.return_value = mock_result
        
        slot_create = SlotDefinitionCreate(
            slot_key="grade",
            type="string",
            required=True,
        )
        
        with pytest.raises(ValueError) as exc_info:
            await service.create_slot_definition("test-tenant", slot_create)
        
        assert "已存在" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_create_slot_definition_invalid_type(self, service, mock_session):
        """[AC-MRS-07] 创建无效类型抛出异常"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        slot_create = SlotDefinitionCreate(
            slot_key="grade",
            type="invalid_type",
            required=True,
        )
        
        with pytest.raises(ValueError) as exc_info:
            await service.create_slot_definition("test-tenant", slot_create)
        
        assert "无效的槽位类型" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_create_slot_definition_with_linked_field(self, service, mock_session):
        """[AC-MRS-08] 创建槽位定义并关联元数据字段"""
        field_id = uuid.uuid4()
        mock_field = MagicMock(spec=MetadataFieldDefinition)
        mock_field.id = field_id
        
        slot_result = MagicMock()
        slot_result.scalar_one_or_none.return_value = None
        
        field_result = MagicMock()
        field_result.scalar_one_or_none.return_value = mock_field
        
        mock_session.execute.side_effect = [slot_result, field_result]
        
        slot_create = SlotDefinitionCreate(
            slot_key="grade",
            type="string",
            required=True,
            linked_field_id=str(field_id),
        )
        
        slot = await service.create_slot_definition("test-tenant", slot_create)
        
        assert slot is not None
        mock_session.add.assert_called_once()

    @pytest.mark.asyncio
    async def test_create_slot_definition_linked_field_not_found(self, service, mock_session):
        """[AC-MRS-08] 关联字段不存在抛出异常"""
        field_id = uuid.uuid4()
        
        slot_result = MagicMock()
        slot_result.scalar_one_or_none.return_value = None
        
        field_result = MagicMock()
        field_result.scalar_one_or_none.return_value = None
        
        mock_session.execute.side_effect = [slot_result, field_result]
        
        slot_create = SlotDefinitionCreate(
            slot_key="grade",
            type="string",
            required=True,
            linked_field_id=str(field_id),
        )
        
        with pytest.raises(ValueError) as exc_info:
            await service.create_slot_definition("test-tenant", slot_create)
        
        assert "关联的元数据字段" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_update_slot_definition(self, service, mock_session):
        """更新槽位定义"""
        slot_id = uuid.uuid4()
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.id = slot_id
        mock_slot.slot_key = "grade"
        mock_slot.type = "string"
        mock_slot.required = False
        mock_slot.extract_strategy = None
        mock_slot.validation_rule = None
        mock_slot.ask_back_prompt = None
        mock_slot.default_value = None
        mock_slot.linked_field_id = None
        mock_slot.updated_at = None
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_slot
        mock_session.execute.return_value = mock_result
        
        slot_update = SlotDefinitionUpdate(
            required=True,
            ask_back_prompt="请输入年级",
        )
        
        slot = await service.update_slot_definition("test-tenant", str(slot_id), slot_update)
        
        assert slot is not None
        mock_session.flush.assert_called_once()

    @pytest.mark.asyncio
    async def test_update_slot_definition_not_found(self, service, mock_session):
        """更新不存在的槽位定义"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        slot_update = SlotDefinitionUpdate(required=True)
        
        slot = await service.update_slot_definition("test-tenant", str(uuid.uuid4()), slot_update)
        
        assert slot is None

    @pytest.mark.asyncio
    async def test_delete_slot_definition(self, service, mock_session):
        """[AC-MRS-16] 删除槽位定义"""
        slot_id = uuid.uuid4()
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.id = slot_id
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_slot
        mock_session.execute.return_value = mock_result
        
        success = await service.delete_slot_definition("test-tenant", str(slot_id))
        
        assert success is True
        mock_session.delete.assert_called_once()
        mock_session.flush.assert_called_once()

    @pytest.mark.asyncio
    async def test_delete_slot_definition_not_found(self, service, mock_session):
        """[AC-MRS-16] 删除不存在的槽位定义"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        success = await service.delete_slot_definition("test-tenant", str(uuid.uuid4()))
        
        assert success is False

    @pytest.mark.asyncio
    async def test_get_slot_definition_with_field(self, service, mock_session):
        """获取槽位定义及关联字段信息"""
        slot_id = uuid.uuid4()
        mock_slot = MagicMock(spec=SlotDefinition)
        mock_slot.id = slot_id
        mock_slot.tenant_id = "test-tenant"
        mock_slot.slot_key = "grade"
        mock_slot.type = "string"
        mock_slot.required = True
        mock_slot.extract_strategy = "llm"
        mock_slot.validation_rule = None
        mock_slot.ask_back_prompt = "请输入年级"
        mock_slot.default_value = None
        mock_slot.linked_field_id = None
        mock_slot.created_at = None
        mock_slot.updated_at = None
        
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_slot
        mock_session.execute.return_value = mock_result
        
        result = await service.get_slot_definition_with_field("test-tenant", str(slot_id))
        
        assert result is not None
        assert result["slot_key"] == "grade"
        assert result["linked_field"] is None
