"""
语义逻辑审查模块单元测试
测试 semantic_logic.py 中的 SemanticLogicReviewer 类
"""

import pytest
import asyncio
import sys
import os
from unittest.mock import Mock, patch, AsyncMock, MagicMock
from typing import Dict, Any

# 添加项目根目录到路径
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from core.construction_review.component.reviewers.semantic_logic import (
    SemanticLogicReviewer,
    semantic_logic_reviewer,
    SEMANTIC_LOGIC_MODEL_CONFIG
)
from core.construction_review.component.reviewers.base_reviewer import ReviewResult


class TestSemanticLogicReviewer:
    """语义逻辑审查器测试类"""
    
    @pytest.fixture
    def reviewer(self):
        """创建审查器实例"""
        return SemanticLogicReviewer()
    
    @pytest.fixture
    def mock_state(self):
        """创建模拟的状态字典"""
        mock_progress_manager = AsyncMock()
        mock_progress_manager.update_stage_progress = AsyncMock()
        
        return {
            "progress_manager": mock_progress_manager,
            "callback_task_id": "test_task_123"
        }
    
    @pytest.fixture
    def sample_review_content(self):
        """示例审查内容"""
        return """
        施工方案概述：
        本工程为高速公路桥梁施工项目，主要包括桥墩基础施工、桥梁上部结构施工等内容。
        施工工期为12个月，计划2024年3月开工，2025年3月竣工。
        """
    
    @pytest.fixture
    def sample_review_references(self):
        """示例审查参考"""
        return "参考标准：《公路桥涵施工技术规范》JTG/T 3650-2020"
    
    def test_reviewer_initialization(self, reviewer):
        """测试审查器初始化"""
        assert reviewer is not None
        assert reviewer.model == SEMANTIC_LOGIC_MODEL_CONFIG["model"]
        assert reviewer.temperature == SEMANTIC_LOGIC_MODEL_CONFIG["temperature"]
        assert reviewer.max_tokens == SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"]
        assert reviewer.client is not None
    
    def test_global_singleton_instance(self):
        """测试全局单例实例"""
        assert semantic_logic_reviewer is not None
        assert isinstance(semantic_logic_reviewer, SemanticLogicReviewer)
    
    def test_model_config(self):
        """测试模型配置"""
        assert SEMANTIC_LOGIC_MODEL_CONFIG["base_url"] == "http://192.168.91.253:8003/v1"
        assert SEMANTIC_LOGIC_MODEL_CONFIG["api_key"] == "sk-123456"
        assert SEMANTIC_LOGIC_MODEL_CONFIG["model"] == "qwen3-30b"
        assert SEMANTIC_LOGIC_MODEL_CONFIG["temperature"] == 0.7
        assert SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"] == 2000
    
    @pytest.mark.asyncio
    async def test_check_semantic_logic_success(
        self, 
        reviewer, 
        sample_review_content, 
        sample_review_references,
        mock_state
    ):
        """测试语义逻辑检查成功场景"""
        # 模拟 OpenAI API 响应
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "审查结果：内容逻辑清晰，无明显问题。"
        
        # 模拟提示词模板
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = "请审查以下内容"
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            # 执行测试
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_001",
                review_content=sample_review_content,
                review_references=sample_review_references,
                review_location_label="第一章",
                state=mock_state,
                stage_name="basic_check"
            )
            
            # 验证结果
            assert isinstance(result, ReviewResult)
            assert result.success is True
            assert result.details["name"] == "semantic_logic_check"
            assert "审查结果" in result.details["response"]
            assert result.error_message is None
            assert result.execution_time is not None
            assert result.execution_time > 0
            
            # 验证 API 调用
            mock_create.assert_called_once()
            call_kwargs = mock_create.call_args.kwargs
            assert call_kwargs["model"] == "qwen3-30b"
            assert call_kwargs["temperature"] == 0.7
            assert call_kwargs["max_tokens"] == 2000
            
            # 验证进度管理器被调用
            mock_state["progress_manager"].update_stage_progress.assert_called()
    
    @pytest.mark.asyncio
    async def test_check_semantic_logic_without_state(
        self, 
        reviewer, 
        sample_review_content
    ):
        """测试没有状态字典的语义逻辑检查"""
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "审查通过"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "system"
        mock_message.content = "系统提示"
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_002",
                review_content=sample_review_content,
                state=None,
                stage_name=None
            )
            
            assert result.success is True
            assert result.details["name"] == "semantic_logic_check"
    
    @pytest.mark.asyncio
    async def test_check_semantic_logic_api_error(
        self, 
        reviewer, 
        sample_review_content,
        mock_state
    ):
        """测试 API 调用失败场景"""
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = "测试内容"
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.side_effect = Exception("API连接失败")
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_003",
                review_content=sample_review_content,
                state=mock_state,
                stage_name="basic_check"
            )
            
            # 验证错误处理
            assert isinstance(result, ReviewResult)
            assert result.success is False
            assert result.error_message is not None
            assert "API连接失败" in result.error_message
            assert result.execution_time is not None
            
            # 验证错误通知被发送
            mock_state["progress_manager"].update_stage_progress.assert_called()
    
    @pytest.mark.asyncio
    async def test_check_semantic_logic_empty_content(
        self, 
        reviewer,
        mock_state
    ):
        """测试空内容场景"""
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "内容为空，无法审查"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = ""
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_004",
                review_content="",
                state=mock_state,
                stage_name="basic_check"
            )
            
            assert result.success is True
            assert result.details["name"] == "semantic_logic_check"
    
    @pytest.mark.asyncio
    async def test_check_semantic_logic_with_references(
        self, 
        reviewer, 
        sample_review_content,
        sample_review_references
    ):
        """测试带参考信息的语义逻辑检查"""
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "根据参考标准，内容符合要求"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = "审查内容"
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_005",
                review_content=sample_review_content,
                review_references=sample_review_references
            )
            
            assert result.success is True
            assert "参考标准" in result.details["response"]
            
            # 验证提示词模板被正确调用
            mock_get_prompt.assert_called_once()
            call_kwargs = mock_get_prompt.call_args.kwargs
            assert call_kwargs["review_content"] == sample_review_content
            assert call_kwargs["review_references"] == sample_review_references
    
    @pytest.mark.asyncio
    async def test_message_format_conversion(self, reviewer, sample_review_content):
        """测试消息格式转换"""
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "测试响应"
        
        # 模拟不同类型的消息
        mock_prompt_template = MagicMock()
        mock_system_msg = MagicMock()
        mock_system_msg.type = "system"
        mock_system_msg.content = "系统提示"
        
        mock_user_msg = MagicMock()
        mock_user_msg.type = "user"
        mock_user_msg.content = "用户输入"
        
        mock_unknown_msg = MagicMock()
        mock_unknown_msg.type = "unknown"
        mock_unknown_msg.content = "未知类型"
        
        mock_prompt_template.format_messages.return_value = [
            mock_system_msg, 
            mock_user_msg, 
            mock_unknown_msg
        ]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_006",
                review_content=sample_review_content
            )
            
            # 验证消息格式转换
            call_kwargs = mock_create.call_args.kwargs
            messages = call_kwargs["messages"]
            
            assert len(messages) == 3
            assert messages[0]["role"] == "system"
            assert messages[0]["content"] == "系统提示"
            assert messages[1]["role"] == "user"
            assert messages[1]["content"] == "用户输入"
            assert messages[2]["role"] == "user"  # unknown类型应转为user
            assert messages[2]["content"] == "未知类型"
    
    @pytest.mark.asyncio
    async def test_execution_time_tracking(self, reviewer, sample_review_content):
        """测试执行时间跟踪"""
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "测试"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = "测试"
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        async def slow_api_call(*args, **kwargs):
            """模拟慢速API调用"""
            await asyncio.sleep(0.1)  # 模拟100ms延迟
            return mock_response
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.side_effect = slow_api_call
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_trace_007",
                review_content=sample_review_content
            )
            
            # 验证执行时间大于100ms
            assert result.execution_time >= 0.1
            assert result.execution_time < 1.0  # 应该不会太长


class TestIntegration:
    """集成测试类"""
    
    @pytest.mark.asyncio
    @pytest.mark.integration
    async def test_full_workflow(self):
        """测试完整工作流程（需要实际API可用）"""
        # 注意：此测试需要实际的API服务可用
        # 在CI/CD环境中可能需要跳过
        pytest.skip("需要实际API服务，跳过集成测试")
        
        reviewer = SemanticLogicReviewer()
        
        result = await reviewer.check_semantic_logic(
            trace_id="integration_test_001",
            review_content="测试施工方案内容",
            review_references="测试参考标准"
        )
        
        assert isinstance(result, ReviewResult)
        assert result.execution_time is not None


class TestEdgeCases:
    """边界情况测试类"""
    
    @pytest.mark.asyncio
    async def test_very_long_content(self, reviewer):
        """测试超长内容"""
        long_content = "测试内容 " * 10000  # 非常长的内容
        
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "内容过长"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = long_content
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_edge_001",
                review_content=long_content
            )
            
            assert result.success is True
    
    @pytest.mark.asyncio
    async def test_special_characters(self, reviewer):
        """测试特殊字符"""
        special_content = "测试内容包含特殊字符：@#$%^&*(){}[]|\\:;\"'<>,.?/~`"
        
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "特殊字符处理正常"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = special_content
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_edge_002",
                review_content=special_content
            )
            
            assert result.success is True
    
    @pytest.mark.asyncio
    async def test_unicode_content(self, reviewer):
        """测试Unicode字符"""
        unicode_content = "测试内容包含各种语言：English, 中文, 日本語, 한국어, Русский, العربية, 🚀🎉"
        
        mock_response = MagicMock()
        mock_response.choices = [MagicMock()]
        mock_response.choices[0].message.content = "Unicode处理正常"
        
        mock_prompt_template = MagicMock()
        mock_message = MagicMock()
        mock_message.type = "user"
        mock_message.content = unicode_content
        mock_prompt_template.format_messages.return_value = [mock_message]
        
        with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
             patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
            
            mock_create.return_value = mock_response
            mock_get_prompt.return_value = mock_prompt_template
            
            result = await reviewer.check_semantic_logic(
                trace_id="test_edge_003",
                review_content=unicode_content
            )
            
            assert result.success is True


if __name__ == "__main__":
    # 运行测试
    pytest.main([__file__, "-v", "-s", "--tb=short"])

