import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from app.workers.llm_adapter import LLMAdapter, LLMAdapterError, BRAND_CITATION_PROMPT


class TestLLMAdapter:
    """LLM适配器测试"""

    @pytest.fixture
    def llm_adapter(self):
        """创建LLM适配器实例"""
        return LLMAdapter()

    @pytest.mark.asyncio
    async def test_llm_adapter_cited_brand(self, llm_adapter):
        """测试检测到品牌引用"""
        mock_response = {
            "cited": True,
            "position": 1,
            "citation_text": "XXX是一款非常优秀的品牌产品",
            "sentiment": "positive",
            "confidence": 0.95
        }

        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.return_value = mock_response

                result = await llm_adapter.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="XXX",
                    brand_aliases=["品牌别名1", "品牌别名2"]
                )

                assert result.cited is True
                assert result.position == 1
                assert result.citation_text == "XXX是一款非常优秀的品牌产品"
                assert result.sentiment == "positive"
                assert result.confidence == 0.95

    @pytest.mark.asyncio
    async def test_llm_adapter_not_cited(self, llm_adapter):
        """测试未检测到品牌引用"""
        mock_response = {
            "cited": False,
            "position": None,
            "citation_text": None,
            "sentiment": "neutral",
            "confidence": 0.90
        }

        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.return_value = mock_response

                result = await llm_adapter.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="YYY",
                    brand_aliases=[]
                )

                assert result.cited is False
                assert result.position is None
                assert result.citation_text is None
                assert result.sentiment == "neutral"

    @pytest.mark.asyncio
    async def test_llm_adapter_sentiment_positive(self, llm_adapter):
        """测试正面情感"""
        mock_response = {
            "cited": True,
            "position": 2,
            "citation_text": "YYY品牌产品质量非常好，用户口碑极佳",
            "sentiment": "positive",
            "confidence": 0.92
        }

        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.return_value = mock_response

                result = await llm_adapter.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="YYY",
                    brand_aliases=[]
                )

                assert result.sentiment == "positive"

    @pytest.mark.asyncio
    async def test_llm_adapter_sentiment_negative(self, llm_adapter):
        """测试负面情感"""
        mock_response = {
            "cited": True,
            "position": 3,
            "citation_text": "ZZZ品牌存在质量问题，遭到用户投诉",
            "sentiment": "negative",
            "confidence": 0.88
        }

        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.return_value = mock_response

                result = await llm_adapter.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="ZZZ",
                    brand_aliases=[]
                )

                assert result.sentiment == "negative"

    @pytest.mark.asyncio
    async def test_llm_adapter_api_error_retry(self, llm_adapter):
        """测试API错误重试"""
        mock_success_response = {
            "cited": True,
            "position": 1,
            "citation_text": "测试文本",
            "sentiment": "neutral",
            "confidence": 0.90
        }

        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.side_effect = [
                    Exception("API调用失败"),
                    Exception("API调用失败"),
                    mock_success_response
                ]

                result = await llm_adapter.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="测试品牌",
                    brand_aliases=[]
                )

                assert result.cited is True
                assert mock_call.call_count == 3

    @pytest.mark.asyncio
    async def test_llm_adapter_parse_error(self, llm_adapter):
        """测试响应解析错误"""
        with patch("app.workers.llm_adapter.settings") as mock_settings:
            mock_settings.ENABLE_LLM = True
            with patch.object(llm_adapter, '_call_deepseek', new_callable=AsyncMock) as mock_call:
                mock_call.return_value = {"invalid": "response"}

                with pytest.raises(LLMAdapterError) as exc_info:
                    await llm_adapter.query_brand_citation(
                        keyword="AI搜索",
                        brand_name="测试品牌",
                        brand_aliases=[]
                    )

                error_msg = str(exc_info.value)
                assert "响应缺少必需字段" in error_msg or "解析响应失败" in error_msg

    def test_build_prompt(self, llm_adapter):
        """测试Prompt构建"""
        prompt = llm_adapter._build_prompt(
            keyword="AI搜索",
            brand_name="测试品牌",
            brand_aliases=["别名1", "别名2"]
        )

        assert "AI搜索" in prompt
        assert "测试品牌" in prompt
        assert "别名1" in prompt
        assert "别名2" in prompt
