"""Tests for BrandCitationLLMService - replacing LLMAdapter"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from app.services.llm.brand_citation_service import BrandCitationLLMService, BRAND_CITATION_PROMPT
from app.services.llm.base import LLMError


class TestBrandCitationLLMServiceInit:
    """服务初始化测试"""

    def test_init_with_default_factory(self):
        service = BrandCitationLLMService()
        assert service is not None
        assert service._provider_name is None
        assert service._model is None

    def test_init_with_custom_provider(self):
        service = BrandCitationLLMService(provider_name="deepseek", model="deepseek-chat")
        assert service._provider_name == "deepseek"
        assert service._model == "deepseek-chat"


class TestBrandCitationLLMServiceQuery:
    """品牌引用查询测试"""

    @pytest.mark.asyncio
    async def test_query_brand_citation_cited(self):
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": true, "position": 1, "citation_text": "Brand X is great", "sentiment": "positive", "confidence": 0.9}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="Brand X",
                    brand_aliases=["BX"]
                )
                assert result.cited is True
                assert result.position == 1
                assert result.confidence == 0.9

    @pytest.mark.asyncio
    async def test_query_brand_citation_not_cited(self):
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": false, "position": null, "citation_text": null, "sentiment": "neutral", "confidence": 0.8}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="Brand X",
                    brand_aliases=[]
                )
                assert result.cited is False
                assert result.position is None
                assert result.citation_text is None

    @pytest.mark.asyncio
    async def test_query_brand_citation_with_markdown_json(self):
        """Test that markdown-wrapped JSON is handled via extract_json"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='```json\n{"cited": true, "position": 2, "citation_text": "test", "sentiment": "positive", "confidence": 0.7}\n```'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="test",
                    brand_name="Brand X",
                    brand_aliases=[]
                )
                assert result.cited is True
                assert result.position == 2

    @pytest.mark.asyncio
    async def test_query_brand_citation_sentiment_positive(self):
        """测试正面情感"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": true, "position": 2, "citation_text": "YYY品牌产品质量非常好", "sentiment": "positive", "confidence": 0.92}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="YYY",
                    brand_aliases=[]
                )
                assert result.sentiment == "positive"

    @pytest.mark.asyncio
    async def test_query_brand_citation_sentiment_negative(self):
        """测试负面情感"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": true, "position": 3, "citation_text": "ZZZ品牌存在质量问题", "sentiment": "negative", "confidence": 0.88}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="AI搜索",
                    brand_name="ZZZ",
                    brand_aliases=[]
                )
                assert result.sentiment == "negative"

    @pytest.mark.asyncio
    async def test_query_brand_citation_invalid_sentiment_defaults_neutral(self):
        """测试无效sentiment值默认为neutral"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": true, "position": 1, "citation_text": "test", "sentiment": "unknown", "confidence": 0.5}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="test",
                    brand_name="Test",
                    brand_aliases=[]
                )
                assert result.sentiment == "neutral"

    @pytest.mark.asyncio
    async def test_query_brand_citation_confidence_clamped(self):
        """测试confidence值被钳制到0.0-1.0范围"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"cited": true, "position": 1, "citation_text": "test", "sentiment": "neutral", "confidence": 1.5}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                result = await service.query_brand_citation(
                    keyword="test",
                    brand_name="Test",
                    brand_aliases=[]
                )
                assert result.confidence == 1.0


class TestBrandCitationLLMServicePrompt:
    """Prompt构建测试"""

    def test_build_prompt(self):
        service = BrandCitationLLMService()
        prompt = service._build_prompt("AI搜索", "Brand X", ["BX"])
        assert "Brand X" in prompt
        assert "BX" in prompt
        assert "AI搜索" in prompt

    def test_build_prompt_no_aliases(self):
        service = BrandCitationLLMService()
        prompt = service._build_prompt("AI搜索", "Brand X", [])
        assert "Brand X" in prompt
        assert "AI搜索" in prompt
        assert "无" in prompt

    def test_brand_citation_prompt_constant_exists(self):
        """验证BRAND_CITATION_PROMPT常量存在且包含关键占位符"""
        assert "{keyword}" in BRAND_CITATION_PROMPT
        assert "{brand_name}" in BRAND_CITATION_PROMPT
        assert "{brand_aliases}" in BRAND_CITATION_PROMPT


class TestBrandCitationLLMServiceErrors:
    """错误处理测试"""

    @pytest.mark.asyncio
    async def test_llm_disabled_raises_error(self):
        service = BrandCitationLLMService()
        with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
            mock_settings.ENABLE_LLM = False
            with pytest.raises(LLMError, match="LLM"):
                await service.query_brand_citation("test", "Brand X", [])

    @pytest.mark.asyncio
    async def test_llm_disabled_error_contains_config_guidance(self):
        """LLM禁用时错误信息应包含配置指引"""
        service = BrandCitationLLMService()
        with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
            mock_settings.ENABLE_LLM = False
            with pytest.raises(LLMError) as exc_info:
                await service.query_brand_citation("test", "Brand X", [])
            error_msg = str(exc_info.value)
            assert "ENABLE_LLM" in error_msg

    @pytest.mark.asyncio
    async def test_missing_required_fields_raises_error(self):
        """响应缺少必需字段时抛出异常"""
        service = BrandCitationLLMService()
        mock_provider = AsyncMock()
        mock_provider.chat.return_value = MagicMock(
            content='{"invalid": "response"}'
        )
        with patch.object(service, '_get_provider', return_value=mock_provider):
            with patch('app.services.llm.brand_citation_service.settings') as mock_settings:
                mock_settings.ENABLE_LLM = True
                with pytest.raises((LLMError, ValueError)):
                    await service.query_brand_citation("test", "Brand X", [])

    @pytest.mark.asyncio
    async def test_no_mock_result_method(self):
        """确保不存在_get_mock_result方法（与旧LLMAdapter的区别）"""
        service = BrandCitationLLMService()
        assert not hasattr(service, "_get_mock_result")
