"""
Tests for Slot Strategy Executor.
[AC-MRS-07-UPGRADE] 提取策略链执行器测试
"""

import pytest
from unittest.mock import AsyncMock, MagicMock

from app.models.entities import ExtractFailureType
from app.services.mid.slot_strategy_executor import (
    SlotStrategyExecutor,
    ExtractContext,
    StrategyChainResult,
    execute_extract_strategies,
)


class TestSlotStrategyExecutor:
    """测试槽位策略执行器"""

    @pytest.fixture
    def executor(self):
        """创建执行器实例"""
        return SlotStrategyExecutor()

    @pytest.fixture
    def context(self):
        """创建测试上下文"""
        return ExtractContext(
            tenant_id="test-tenant",
            slot_key="grade",
            user_input="我想了解初一语文课程",
            slot_type="string",
        )

    @pytest.mark.asyncio
    async def test_execute_chain_success_on_first_step(self, executor, context):
        """测试第一步成功时停止"""
        # Mock rule extractor 成功
        mock_rule = AsyncMock(return_value="初一")
        executor._extractors["rule"] = mock_rule

        result = await executor.execute_chain(
            strategies=["rule", "llm", "user_input"],
            context=context,
        )

        assert result.success is True
        assert result.final_value == "初一"
        assert result.final_strategy == "rule"
        assert len(result.steps) == 1
        assert result.steps[0].success is True
        mock_rule.assert_called_once()

    @pytest.mark.asyncio
    async def test_execute_chain_fallback_to_second_step(self, executor, context):
        """测试第一步失败，第二步成功"""
        # Mock rule extractor 失败（返回空）
        mock_rule = AsyncMock(return_value=None)
        # Mock llm extractor 成功
        mock_llm = AsyncMock(return_value="初一")
        
        executor._extractors["rule"] = mock_rule
        executor._extractors["llm"] = mock_llm

        result = await executor.execute_chain(
            strategies=["rule", "llm", "user_input"],
            context=context,
        )

        assert result.success is True
        assert result.final_value == "初一"
        assert result.final_strategy == "llm"
        assert len(result.steps) == 2
        assert result.steps[0].success is False
        assert result.steps[0].failure_type == ExtractFailureType.EXTRACT_EMPTY
        assert result.steps[1].success is True

    @pytest.mark.asyncio
    async def test_execute_chain_all_failed(self, executor, context):
        """测试所有策略都失败"""
        # Mock 所有 extractor 都失败
        mock_rule = AsyncMock(return_value=None)
        mock_llm = AsyncMock(return_value=None)
        mock_user_input = AsyncMock(return_value=None)
        
        executor._extractors["rule"] = mock_rule
        executor._extractors["llm"] = mock_llm
        executor._extractors["user_input"] = mock_user_input

        result = await executor.execute_chain(
            strategies=["rule", "llm", "user_input"],
            context=context,
            ask_back_prompt="请告诉我您的年级",
        )

        assert result.success is False
        assert result.final_value is None
        assert result.final_strategy is None
        assert len(result.steps) == 3
        assert result.ask_back_prompt == "请告诉我您的年级"
        
        # 所有步骤都失败
        for step in result.steps:
            assert step.success is False
            assert step.failure_type == ExtractFailureType.EXTRACT_EMPTY

    @pytest.mark.asyncio
    async def test_execute_chain_validation_failure(self, executor, context):
        """测试校验失败的情况"""
        context.validation_rule = r"^初[一二三]$"  # 只允许初一/初二/初三
        
        # Mock rule extractor 返回不符合校验的值
        mock_rule = AsyncMock(return_value="高一")
        executor._extractors["rule"] = mock_rule

        result = await executor.execute_chain(
            strategies=["rule"],
            context=context,
        )

        assert result.success is False
        assert result.steps[0].failure_type == ExtractFailureType.EXTRACT_VALIDATION_FAIL
        assert "Validation failed" in result.steps[0].failure_reason

    @pytest.mark.asyncio
    async def test_execute_chain_runtime_error(self, executor, context):
        """测试运行时错误"""
        # Mock rule extractor 抛出异常
        mock_rule = AsyncMock(side_effect=Exception("LLM service unavailable"))
        executor._extractors["rule"] = mock_rule

        result = await executor.execute_chain(
            strategies=["rule"],
            context=context,
        )

        assert result.success is False
        assert result.steps[0].failure_type == ExtractFailureType.EXTRACT_RUNTIME_ERROR
        assert "Runtime error" in result.steps[0].failure_reason

    @pytest.mark.asyncio
    async def test_execute_chain_empty_strategies(self, executor, context):
        """测试空策略链"""
        result = await executor.execute_chain(
            strategies=[],
            context=context,
        )

        assert result.success is False
        assert len(result.steps) == 0

    @pytest.mark.asyncio
    async def test_execute_chain_unknown_strategy(self, executor, context):
        """测试未知策略"""
        result = await executor.execute_chain(
            strategies=["unknown_strategy"],
            context=context,
        )

        assert result.success is False
        assert result.steps[0].failure_type == ExtractFailureType.EXTRACT_RUNTIME_ERROR
        assert "Unknown strategy" in result.steps[0].failure_reason

    @pytest.mark.asyncio
    async def test_execute_chain_result_to_dict(self, executor, context):
        """测试结果转换为字典"""
        mock_rule = AsyncMock(return_value="初一")
        executor._extractors["rule"] = mock_rule

        result = await executor.execute_chain(
            strategies=["rule"],
            context=context,
        )

        result_dict = result.to_dict()
        
        assert result_dict["slot_key"] == "grade"
        assert result_dict["success"] is True
        assert result_dict["final_value"] == "初一"
        assert result_dict["final_strategy"] == "rule"
        assert "steps" in result_dict
        assert "total_execution_time_ms" in result_dict


class TestExecuteExtractStrategies:
    """测试便捷函数"""

    @pytest.mark.asyncio
    async def test_convenience_function(self):
        """测试便捷函数 execute_extract_strategies"""
        mock_rule = AsyncMock(return_value="初一")
        
        result = await execute_extract_strategies(
            strategies=["rule"],
            tenant_id="test-tenant",
            slot_key="grade",
            user_input="我想了解初一语文课程",
            rule_extractor=mock_rule,
        )

        assert result.success is True
        assert result.final_value == "初一"

    @pytest.mark.asyncio
    async def test_convenience_function_with_validation(self):
        """测试带校验的便捷函数"""
        mock_rule = AsyncMock(return_value="初一")
        
        result = await execute_extract_strategies(
            strategies=["rule"],
            tenant_id="test-tenant",
            slot_key="grade",
            user_input="我想了解初一语文课程",
            validation_rule=r"^初[一二三]$",
            rule_extractor=mock_rule,
        )

        assert result.success is True
        assert result.final_value == "初一"


class TestExtractContext:
    """测试提取上下文"""

    def test_context_creation(self):
        """测试上下文创建"""
        context = ExtractContext(
            tenant_id="test-tenant",
            slot_key="grade",
            user_input="测试输入",
            slot_type="string",
            validation_rule=r"^初[一二三]$",
            history=[{"role": "user", "content": "你好"}],
            session_id="session-123",
        )

        assert context.tenant_id == "test-tenant"
        assert context.slot_key == "grade"
        assert context.user_input == "测试输入"
        assert context.slot_type == "string"
        assert context.validation_rule == r"^初[一二三]$"
        assert len(context.history) == 1
        assert context.session_id == "session-123"
