"""
Tests for Slot State Aggregator.
[AC-MRS-SLOT-META-01] 槽位状态聚合服务测试
"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from app.models.mid.schemas import MemorySlot, SlotSource
from app.services.mid.slot_state_aggregator import (
    SlotState,
    SlotStateAggregator,
    create_slot_state_aggregator,
)


class TestSlotState:
    """测试 SlotState 数据类"""

    def test_slot_state_initialization(self):
        """测试 SlotState 初始化"""
        state = SlotState()
        assert state.filled_slots == {}
        assert state.missing_required_slots == []
        assert state.slot_sources == {}
        assert state.slot_confidence == {}
        assert state.slot_to_field_map == {}

    def test_get_value_for_filter_direct_match(self):
        """测试直接匹配获取过滤值"""
        state = SlotState(
            filled_slots={"product_line": "vip_course"},
            slot_to_field_map={},
        )
        value = state.get_value_for_filter("product_line")
        assert value == "vip_course"

    def test_get_value_for_filter_via_mapping(self):
        """测试通过映射获取过滤值"""
        state = SlotState(
            filled_slots={"product": "vip_course"},
            slot_to_field_map={"product": "product_line"},
        )
        value = state.get_value_for_filter("product_line")
        assert value == "vip_course"

    def test_get_value_for_filter_not_found(self):
        """测试获取不存在的过滤值"""
        state = SlotState(filled_slots={})
        value = state.get_value_for_filter("non_existent")
        assert value is None

    def test_to_debug_info(self):
        """测试转换为调试信息"""
        state = SlotState(
            filled_slots={"key": "value"},
            missing_required_slots=[{"slot_key": "missing"}],
        )
        debug_info = state.to_debug_info()
        assert debug_info["filled_slots"] == {"key": "value"}
        assert len(debug_info["missing_required_slots"]) == 1


class TestSlotStateAggregator:
    """测试 SlotStateAggregator"""

    @pytest.fixture
    def mock_session(self):
        """模拟数据库会话"""
        return AsyncMock()

    @pytest.fixture
    def aggregator(self, mock_session):
        """创建聚合器实例"""
        return SlotStateAggregator(
            session=mock_session,
            tenant_id="test_tenant",
        )

    @pytest.mark.asyncio
    async def test_aggregate_from_memory_slots(self, aggregator, mock_session):
        """测试从 memory_slots 初始化"""
        memory_slots = {
            "product_line": MemorySlot(
                key="product_line",
                value="vip_course",
                source=SlotSource.USER_CONFIRMED,
                confidence=1.0,
            )
        }

        # 模拟槽位定义服务返回空列表（没有 required 槽位）
        with patch.object(
            aggregator._slot_def_service,
            "list_slot_definitions",
            return_value=[],
        ):
            state = await aggregator.aggregate(
                memory_slots=memory_slots,
                current_input_slots=None,
                context=None,
            )

        assert state.filled_slots["product_line"] == "vip_course"
        assert state.slot_sources["product_line"] == "user_confirmed"
        assert state.slot_confidence["product_line"] == 1.0

    @pytest.mark.asyncio
    async def test_aggregate_current_input_priority(self, aggregator, mock_session):
        """测试当前输入优先级高于 memory"""
        memory_slots = {
            "product_line": MemorySlot(
                key="product_line",
                value="old_value",
                source=SlotSource.USER_CONFIRMED,
                confidence=1.0,
            )
        }
        current_input = {"product_line": "new_value"}

        with patch.object(
            aggregator._slot_def_service,
            "list_slot_definitions",
            return_value=[],
        ):
            state = await aggregator.aggregate(
                memory_slots=memory_slots,
                current_input_slots=current_input,
                context=None,
            )

        # 当前输入应该覆盖 memory 的值
        assert state.filled_slots["product_line"] == "new_value"
        assert state.slot_sources["product_line"] == "user_confirmed"

    @pytest.mark.asyncio
    async def test_aggregate_extract_from_context(self, aggregator, mock_session):
        """测试从 context 提取槽位值"""
        context = {
            "scene": "open_consult",
            "product_line": "vip_course",
            "other_key": "other_value",
        }

        with patch.object(
            aggregator._slot_def_service,
            "list_slot_definitions",
            return_value=[],
        ):
            state = await aggregator.aggregate(
                memory_slots=None,
                current_input_slots=None,
                context=context,
            )

        # 应该提取 scene 和 product_line
        assert state.filled_slots.get("scene") == "open_consult"
        assert state.filled_slots.get("product_line") == "vip_course"
        assert state.slot_sources.get("scene") == "context"

    @pytest.mark.asyncio
    async def test_generate_ask_back_response_with_prompt(self, aggregator):
        """测试生成追问响应 - 使用配置的 ask_back_prompt"""
        state = SlotState(
            missing_required_slots=[
                {
                    "slot_key": "region",
                    "label": "地区",
                    "reason": "required_slot_missing",
                    "ask_back_prompt": "请问您在哪个地区？",
                }
            ]
        )

        response = await aggregator.generate_ask_back_response(state)
        assert response == "请问您在哪个地区？"

    @pytest.mark.asyncio
    async def test_generate_ask_back_response_generic(self, aggregator):
        """测试生成追问响应 - 使用通用模板"""
        state = SlotState(
            missing_required_slots=[
                {
                    "slot_key": "region",
                    "label": "地区",
                    "reason": "required_slot_missing",
                    # 没有 ask_back_prompt
                }
            ]
        )

        response = await aggregator.generate_ask_back_response(state)
        assert "地区" in response

    @pytest.mark.asyncio
    async def test_generate_ask_back_response_no_missing(self, aggregator):
        """测试没有缺失槽位时返回 None"""
        state = SlotState(missing_required_slots=[])
        response = await aggregator.generate_ask_back_response(state)
        assert response is None


class TestCreateSlotStateAggregator:
    """测试工厂函数"""

    def test_create_aggregator(self):
        """测试创建聚合器实例"""
        mock_session = MagicMock()
        aggregator = create_slot_state_aggregator(
            session=mock_session,
            tenant_id="test_tenant",
        )
        assert isinstance(aggregator, SlotStateAggregator)
        assert aggregator._tenant_id == "test_tenant"


class TestSlotStateFilterPriority:
    """测试过滤值来源优先级"""

    @pytest.mark.asyncio
    async def test_filter_priority_slot_first(self):
        """测试优先级：slot > context > default"""
        from unittest.mock import AsyncMock, MagicMock
        
        mock_session = AsyncMock()
        aggregator = SlotStateAggregator(
            session=mock_session,
            tenant_id="test_tenant",
        )

        # 模拟槽位定义
        mock_slot_def = MagicMock()
        mock_slot_def.slot_key = "product"
        mock_slot_def.linked_field_id = None
        mock_slot_def.required = False

        with patch.object(
            aggregator._slot_def_service,
            "list_slot_definitions",
            return_value=[mock_slot_def],
        ):
            state = await aggregator.aggregate(
                memory_slots={
                    "product": MemorySlot(
                        key="product",
                        value="from_memory",
                        source=SlotSource.USER_CONFIRMED,
                        confidence=1.0,
                    )
                },
                current_input_slots={"product": "from_input"},
                context={"product": "from_context"},
            )

        # 当前输入应该优先级最高
        assert state.filled_slots["product"] == "from_input"
