
import os
import sys
import json
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
from langchain_core.messages import AIMessage

from alayaflow.component.chat_model import mk_chat_model_deepseek

sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest

# import function to be tested
from alayaflow.component.intent_classifier import execute_intent_classification, IntentionResult

class TestIntentClassifier:
    """测试execute_intent_classification函数的测试类"""

    @pytest.fixture
    def mock_model(self):
        """创建一个模拟的ChatOpenAI模型"""
        mock_model = Mock()
        mock_model.invoke = Mock()
        return mock_model

    @pytest.fixture
    def test_data(self):
        """测试数据集"""
        return {
            "scenario_1": {
                "query": "今天天气怎么样？",
                "intents": ["查询天气", "播放音乐", "设置提醒"],
                "expected_response": {"classification_id": 0, "reason": "用户明确询问了天气情况"},
            },
            "scenario_2": {
                "query": "我想听周杰伦的歌",
                "intents": ["查询天气", "播放音乐", "设置提醒"],
                "expected_response": {"classification_id": 1, "reason": "用户想要听音乐"},
            },
            "scenario_3": {
                "query": "帮我设置明天上午9点的闹钟",
                "intents": ["查询天气", "播放音乐", "设置提醒"],
                "expected_response": {"classification_id": 2, "reason": "用户需要设置提醒"},
            },
            "scenario_4": {
                "query": "这个问题不匹配任何意图",
                "intents": ["查询天气", "播放音乐", "设置提醒"],
                "expected_response": {"classification_id": -1, "reason": "与所有选项均不匹配"},
            },
            "scenario_5": {
                "query": "测试空意图列表",
                "intents": [],
                "expected_response": {"classification_id": -1, "reason": "input intent list is empty"},
            }
        }

    def test_intent_classifier_with_empty_intents(self, mock_model, test_data):
        """测试空意图列表的情况"""
        scenario = test_data["scenario_5"]
        query = scenario["query"]
        intents = scenario["intents"]
        expected_response = scenario["expected_response"]

        # 设置mock
        mock_model.invoke = Mock(return_value=expected_response)

        # 调用被测试函数
        result = execute_intent_classification(query, intents, model=mock_model)

        # 验证结果
        assert result == IntentionResult(**expected_response)
        # 验证模型方法被调用
        mock_model.invoke.assert_not_called()

    def test_intent_classification_successful_match(self, mock_model, test_data):
        """测试成功匹配意图的情况"""
        scenario = test_data["scenario_1"]
        query = scenario["query"]
        intents = scenario["intents"]
        expected_response = scenario["expected_response"]

        # 创建模拟的invoke方法
        def mock_invoke(messages: list, response_format: dict) -> AIMessage:
            """模拟的invoke方法，检查prompt内容并返回预设响应"""

            # 检查query是否在content中
            assert len(messages) == 2, "messages should have 2 elements"
            sys_role, sys_content = messages[0]
            user_role, user_content = messages[1]
            assert sys_role == "system", "First message role must be 'system'"
            assert user_role == "user", "Second message role must be 'user'"

            # 检查所有intent是否在content中
            for intent in intents:
                assert intent in sys_content, f"Intent '{intent}' not found in user prompt content"

            # 检查query是否在content中
            assert query in user_content, f"Query '{query}' not found in user prompt content"

            # 检查response_format是否为json_object
            assert response_format == {"type": "json_object"}, "response_format should be {'type': 'json_object'}"

            # 返回预设的响应
            return AIMessage(content=json.dumps(expected_response))

        # 设置mock
        mock_model.invoke = Mock(side_effect=mock_invoke)

        # 调用被测试函数
        result = execute_intent_classification(query, intents, model=mock_model)

        # 验证结果
        assert result == IntentionResult(**expected_response)
        # 验证模型方法被调用
        mock_model.invoke.assert_called_once()

    def test_intent_classification_fallback(self, mock_model, test_data):
        """测试回退到fallback_index的情况"""
        scenario = test_data["scenario_4"]
        query = scenario["query"]
        intents = scenario["intents"]
        expected_response = scenario["expected_response"]

        # 创建模拟的invoke方法
        def mock_invoke(messages: list, response_format: dict) -> AIMessage:
            """模拟的invoke方法，检查prompt内容并返回预设响应"""

            # 检查query是否在content中
            assert len(messages) == 2, "messages should have 2 elements"
            sys_role, sys_content = messages[0]
            user_role, user_content = messages[1]
            assert sys_role == "system", "First message role must be 'system'"
            assert user_role == "user", "Second message role must be 'user'"

            # 检查所有intent是否在content中
            for intent in intents:
                assert intent in sys_content, f"Intent '{intent}' not found in user prompt content"

            # 检查query是否在content中
            assert query in user_content, f"Query '{query}' not found in user prompt content"

            # 检查response_format是否为json_object
            assert response_format == {"type": "json_object"}, "response_format should be {'type': 'json_object'}"

            # 返回预设的响应
            return AIMessage(content=json.dumps(expected_response))

        # 设置mock
        mock_model.invoke = Mock(side_effect=mock_invoke)

        # 调用被测试函数
        fallback_index = -1
        result = execute_intent_classification(query, intents, model=mock_model, fallback_index=fallback_index)

        # 验证结果
        assert result == IntentionResult(**expected_response)
        assert result.classification_id == fallback_index
        
        # 验证模型方法被调用
        mock_model.invoke.assert_called_once()


    def test_intent_classifier_multiple_scenarios(self, mock_model, test_data):
        """测试多个场景"""
        scenarios_to_test = ["scenario_1", "scenario_2", "scenario_3"]

        for scenario_name in scenarios_to_test:
            scenario = test_data[scenario_name]
            query = scenario["query"]
            intents = scenario["intents"]
            expected_response = scenario["expected_response"]

            # 重置mock
            mock_model.reset_mock()

            # 设置模拟响应
            mock_model.invoke.return_value = AIMessage(content=json.dumps(expected_response))

            # 调用被测试函数
            result = execute_intent_classification(query, intents, model=mock_model)

            # 验证结果
            assert result == IntentionResult(**expected_response)

            # 验证模型被调用
            mock_model.invoke.assert_called_once()

            # 验证调用参数
            call_args = mock_model.invoke.call_args
            messages = call_args[0][0]  # 位置参数的第1个值 → messages列表
            response_format = call_args[1]["response_format"] # 关键字参数的response_format值
            # 检查query是否在content中
            assert len(messages) == 2, "messages should have 2 elements"
            sys_role, sys_content = messages[0]
            user_role, user_content = messages[1]
            assert sys_role == "system", "First message role must be 'system'"
            assert user_role == "user", "Second message role must be 'user'"

            # 检查所有intent是否在content中
            for intent in intents:
                assert intent in sys_content, f"Intent '{intent}' not found in user prompt content"

            # 检查query是否在content中
            assert query in user_content, f"Query '{query}' not found in user prompt content"

            # 检查response_format是否为json_object
            assert response_format == {"type": "json_object"}, "response_format should be {'type': 'json_object'}"
    
    @pytest.mark.skipif(
        not os.getenv("ENABLE_REAL_LLM_TEST"),
        reason="未开启真实LLM测试开关（设置ENABLE_REAL_LLM_TEST=true可运行）"
    )
    def test_intent_classifier_multiple_scenarios_real(self, test_data):
        """测试多个场景"""
        scenarios_to_test = ["scenario_1", "scenario_2", "scenario_3"]

        for scenario_name in scenarios_to_test:
            scenario = test_data[scenario_name]
            query = scenario["query"]
            intents = scenario["intents"]
            expected_response = scenario["expected_response"]

            model = mk_chat_model_deepseek()

            # 调用被测试函数
            result = execute_intent_classification(query, intents, model=model)

            # 验证结果
            assert isinstance(result, IntentionResult), "result should be an instance of IntentionResult"
            assert result.classification_id == expected_response["classification_id"]
    

if __name__ == "__main__":
    pytest.main([__file__, "-v"])

