"""
Tests for KB Search Dynamic Tool with Slot State Integration.
[AC-MRS-SLOT-META-02] KB 检索与槽位状态集成测试
"""

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

from app.models.mid.schemas import MemorySlot, SlotSource, ToolCallStatus
from app.services.mid.kb_search_dynamic_tool import (
    KbSearchDynamicConfig,
    KbSearchDynamicTool,
)
from app.services.mid.slot_state_aggregator import SlotState


class TestKbSearchDynamicWithSlotState:
    """测试 KB Search Dynamic Tool 与槽位状态的集成"""

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

    @pytest.fixture
    def kb_tool(self, mock_session):
        """创建 KB 工具实例"""
        return KbSearchDynamicTool(
            session=mock_session,
            config=KbSearchDynamicConfig(
                enabled=True,
                top_k=5,
                timeout_ms=10000,
                min_score_threshold=0.5,
            ),
        )

    @pytest.mark.asyncio
    async def test_execute_with_missing_required_slots(self, kb_tool, mock_session):
        """测试当存在缺失必填槽位时返回追问响应"""
        slot_state = SlotState(
            filled_slots={},
            missing_required_slots=[
                {
                    "slot_key": "product_line",
                    "label": "产品线",
                    "reason": "required_slot_missing",
                    "ask_back_prompt": "请问您咨询的是哪个产品线？",
                }
            ],
        )

        result = await kb_tool.execute(
            query="退款政策",
            tenant_id="test_tenant",
            context={},
            slot_state=slot_state,
        )

        assert result.success is False
        assert result.fallback_reason_code == "MISSING_REQUIRED_SLOTS"
        assert len(result.missing_required_slots) == 1
        assert result.missing_required_slots[0]["slot_key"] == "product_line"
        assert result.tool_trace is not None
        assert result.tool_trace.error_code == "MISSING_REQUIRED_SLOTS"

    @pytest.mark.asyncio
    async def test_execute_with_filled_slots(self, kb_tool, mock_session):
        """测试使用已填充槽位构建过滤器"""
        slot_state = SlotState(
            filled_slots={"product_line": "vip_course"},
            missing_required_slots=[],
            slot_to_field_map={"product_line": "product_line"},
        )

        # 模拟 filter_builder
        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[])
        kb_tool._filter_builder = mock_filter_builder

        # 模拟检索结果
        with patch.object(kb_tool, "_retrieve_with_timeout", return_value=[]):
            result = await kb_tool.execute(
                query="退款政策",
                tenant_id="test_tenant",
                context={},
                slot_state=slot_state,
            )

        # 应该成功执行（虽然没有命中结果）
        assert result.success is True

    @pytest.mark.asyncio
    async def test_build_filter_from_slot_state_priority(self, kb_tool, mock_session):
        """测试过滤值来源优先级：slot > context > default"""
        from app.services.mid.metadata_filter_builder import FilterFieldInfo

        slot_state = SlotState(
            filled_slots={"product_line": "from_slot"},
            missing_required_slots=[],
            slot_to_field_map={"product_line": "product_line"},
        )

        context = {"product_line": "from_context"}

        # 模拟可过滤字段
        mock_field = FilterFieldInfo(
            field_key="product_line",
            label="产品线",
            field_type="string",
            required=True,
            options=None,
            default_value="from_default",
            is_filterable=True,
        )

        # 模拟 filter_builder
        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[mock_field])
        mock_filter_builder._build_field_filter = MagicMock(return_value={"$eq": "from_slot"})
        kb_tool._filter_builder = mock_filter_builder

        filter_result = await kb_tool._build_filter_from_slot_state(
            tenant_id="test_tenant",
            slot_state=slot_state,
            context=context,
        )

        # 应该使用 slot 的值（优先级最高）
        assert "product_line" in filter_result
        mock_filter_builder._build_field_filter.assert_called_once()
        call_args = mock_filter_builder._build_field_filter.call_args
        assert call_args[0][1] == "from_slot"

    @pytest.mark.asyncio
    async def test_build_filter_uses_context_when_slot_empty(self, kb_tool, mock_session):
        """测试当 slot 为空时使用 context 值"""
        from app.services.mid.metadata_filter_builder import FilterFieldInfo

        slot_state = SlotState(
            filled_slots={},  # 空槽位
            missing_required_slots=[],
            slot_to_field_map={},
        )

        context = {"product_line": "from_context"}

        # 模拟可过滤字段
        mock_field = FilterFieldInfo(
            field_key="product_line",
            label="产品线",
            field_type="string",
            required=True,
            options=None,
            default_value="from_default",
            is_filterable=True,
        )

        # 模拟 filter_builder
        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[mock_field])
        mock_filter_builder._build_field_filter = MagicMock(return_value={"$eq": "from_context"})
        kb_tool._filter_builder = mock_filter_builder

        filter_result = await kb_tool._build_filter_from_slot_state(
            tenant_id="test_tenant",
            slot_state=slot_state,
            context=context,
        )

        # 应该使用 context 的值
        assert "product_line" in filter_result
        call_args = mock_filter_builder._build_field_filter.call_args
        assert call_args[0][1] == "from_context"

    @pytest.mark.asyncio
    async def test_build_filter_uses_default_when_no_other(self, kb_tool, mock_session):
        """测试当 slot 和 context 都为空时使用默认值"""
        from app.services.mid.metadata_filter_builder import FilterFieldInfo

        slot_state = SlotState(
            filled_slots={},
            missing_required_slots=[],
            slot_to_field_map={},
        )

        context = {}

        # 模拟可过滤字段（带默认值）
        mock_field = FilterFieldInfo(
            field_key="product_line",
            label="产品线",
            field_type="string",
            required=False,  # 非必填
            options=None,
            default_value="from_default",
            is_filterable=True,
        )

        # 模拟 filter_builder
        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[mock_field])
        mock_filter_builder._build_field_filter = MagicMock(return_value={"$eq": "from_default"})
        kb_tool._filter_builder = mock_filter_builder

        filter_result = await kb_tool._build_filter_from_slot_state(
            tenant_id="test_tenant",
            slot_state=slot_state,
            context=context,
        )

        # 应该使用默认值
        assert "product_line" in filter_result
        call_args = mock_filter_builder._build_field_filter.call_args
        assert call_args[0][1] == "from_default"


class TestKbSearchDynamicSlotMapping:
    """测试槽位与字段映射"""

    @pytest.mark.asyncio
    async def test_slot_to_field_mapping_in_filter(self):
        """测试通过 slot_to_field_map 映射槽位值到字段"""
        from app.services.mid.metadata_filter_builder import FilterFieldInfo
        from unittest.mock import AsyncMock

        mock_session = AsyncMock()
        kb_tool = KbSearchDynamicTool(session=mock_session)

        # slot_key 是 "product"，但映射到 field_key "product_line"
        slot_state = SlotState(
            filled_slots={"product": "vip_course"},
            missing_required_slots=[],
            slot_to_field_map={"product": "product_line"},
        )

        # 模拟可过滤字段（使用 field_key）
        mock_field = FilterFieldInfo(
            field_key="product_line",
            label="产品线",
            field_type="string",
            required=True,
            options=None,
            default_value=None,
            is_filterable=True,
        )

        # 模拟 filter_builder
        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[mock_field])
        mock_filter_builder._build_field_filter = MagicMock(return_value={"$eq": "vip_course"})
        kb_tool._filter_builder = mock_filter_builder

        filter_result = await kb_tool._build_filter_from_slot_state(
            tenant_id="test_tenant",
            slot_state=slot_state,
            context={},
        )

        # 应该通过映射找到值
        assert "product_line" in filter_result
        call_args = mock_filter_builder._build_field_filter.call_args
        assert call_args[0][1] == "vip_course"


class TestKbSearchDynamicDebugInfo:
    """测试调试信息输出"""

    @pytest.mark.asyncio
    async def test_filter_debug_includes_sources(self):
        """测试过滤器调试信息包含来源标识"""
        from app.services.mid.metadata_filter_builder import FilterFieldInfo
        from unittest.mock import AsyncMock

        mock_session = AsyncMock()
        kb_tool = KbSearchDynamicTool(session=mock_session)

        slot_state = SlotState(
            filled_slots={"product_line": "vip_course"},
            missing_required_slots=[],
            slot_to_field_map={"product_line": "product_line"},
        )

        mock_field = FilterFieldInfo(
            field_key="product_line",
            label="产品线",
            field_type="string",
            required=True,
            options=None,
            default_value=None,
            is_filterable=True,
        )

        mock_filter_builder = MagicMock()
        mock_filter_builder._get_filterable_fields = AsyncMock(return_value=[mock_field])
        mock_filter_builder._build_field_filter = MagicMock(return_value={"$eq": "vip_course"})
        kb_tool._filter_builder = mock_filter_builder

        with patch.object(kb_tool, "_retrieve_with_timeout", return_value=[]):
            result = await kb_tool.execute(
                query="退款政策",
                tenant_id="test_tenant",
                context={},
                slot_state=slot_state,
            )

        # 调试信息应该包含来源
        assert result.filter_debug is not None
