"""
测试：LLM 模块
测试 LLM 提供商和工厂方法
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import os

from playwright_ez.ai.types import LLMConfig, LLMProvider
from playwright_ez.ai.llm.base import BaseLLMProvider, LLMResponse
from playwright_ez.ai.llm.claude import ClaudeProvider
from playwright_ez.ai.llm.openai import OpenAIProvider
from playwright_ez.ai.llm.ollama import OllamaProvider
from playwright_ez.ai.llm.custom import CustomProvider
from playwright_ez.ai.llm.factory import create_llm_provider


class TestLLMResponse:
    """测试 LLMResponse 数据类"""

    def test_success_response(self):
        """测试成功响应"""
        response = LLMResponse(
            content="测试内容",
            success=True,
            tokens_used=100
        )
        assert response.content == "测试内容"
        assert response.success is True
        assert response.error is None
        assert response.tokens_used == 100

    def test_error_response(self):
        """测试错误响应"""
        response = LLMResponse(
            content="",
            success=False,
            error="API 错误"
        )
        assert response.content == ""
        assert response.success is False
        assert response.error == "API 错误"
        assert response.tokens_used == 0


class TestClaudeProvider:
    """测试 Claude 提供商"""

    def test_init_with_api_key(self):
        """测试使用 API Key 初始化"""
        config = LLMConfig(
            provider=LLMProvider.CLAUDE,
            claude_api_key="sk-test-key",
            claude_model="claude-sonnet-4-20250514"
        )
        provider = ClaudeProvider(config)
        assert provider.config == config

    @pytest.mark.asyncio
    async def test_is_available_with_api_key(self):
        """测试 API Key 存在时可用"""
        config = LLMConfig(
            provider=LLMProvider.CLAUDE,
            claude_api_key="sk-test-key"
        )
        provider = ClaudeProvider(config)

        with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-test-key"}):
            # Mock anthropic 模块
            with patch.dict('sys.modules', {'anthropic': MagicMock()}):
                result = await provider.is_available()
                # 如果没有 anthropic 包，返回 False
                # 如果有，应该返回 True

    @pytest.mark.asyncio
    async def test_is_available_without_api_key(self):
        """测试没有 API Key 时不可用"""
        config = LLMConfig(provider=LLMProvider.CLAUDE)
        provider = ClaudeProvider(config)

        # 清除环境变量
        with patch.dict(os.environ, {}, clear=True):
            result = await provider.is_available()
            assert result is False


class TestOpenAIProvider:
    """测试 OpenAI 提供商"""

    def test_init_with_api_key(self):
        """测试使用 API Key 初始化"""
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            openai_api_key="sk-openai-key",
            openai_model="gpt-4o"
        )
        provider = OpenAIProvider(config)
        assert provider.config == config

    def test_init_with_base_url(self):
        """测试使用自定义 Base URL 初始化"""
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            openai_api_key="sk-openai-key",
            openai_base_url="https://api.custom.com/v1"
        )
        provider = OpenAIProvider(config)
        assert provider.config.openai_base_url == "https://api.custom.com/v1"

    @pytest.mark.asyncio
    async def test_is_available_without_api_key(self):
        """测试没有 API Key 时不可用"""
        config = LLMConfig(provider=LLMProvider.OPENAI)
        provider = OpenAIProvider(config)

        with patch.dict(os.environ, {}, clear=True):
            result = await provider.is_available()
            assert result is False


class TestOllamaProvider:
    """测试 Ollama 提供商"""

    def test_init(self):
        """测试初始化"""
        config = LLMConfig(
            provider=LLMProvider.OLLAMA,
            ollama_model="llama3",
            ollama_base_url="http://localhost:11434"
        )
        provider = OllamaProvider(config)
        assert provider.config == config

    @pytest.mark.asyncio
    async def test_is_available_when_server_down(self):
        """测试服务器不可用时"""
        config = LLMConfig(provider=LLMProvider.OLLAMA)
        provider = OllamaProvider(config)

        # 当 aiohttp 未安装或服务器不可用时，is_available 应该返回 False
        # 由于 aiohttp 是可选依赖，这里跳过模拟测试
        # 实际测试中如果没有 aiohttp，会直接返回 False
        try:
            import aiohttp
            # 如果有 aiohttp，模拟连接失败
            with patch('aiohttp.ClientSession') as mock_session:
                mock_instance = AsyncMock()
                mock_session.return_value.__aenter__ = AsyncMock(return_value=mock_instance)
                mock_instance.get.side_effect = Exception("Connection refused")
                result = await provider.is_available()
                assert result is False
        except ImportError:
            # 没有 aiohttp 时，is_available 返回 False
            result = await provider.is_available()
            assert result is False


class TestCustomProvider:
    """测试自定义提供商"""

    def test_init_with_call_fn(self):
        """测试使用自定义函数初始化"""
        def my_llm(prompt: str) -> str:
            return f"Response to: {prompt}"

        config = LLMConfig(
            provider=LLMProvider.CUSTOM,
            custom_call_fn=my_llm
        )
        provider = CustomProvider(config)
        assert provider.call_fn == my_llm

    @pytest.mark.asyncio
    async def test_call_success(self):
        """测试调用自定义函数成功"""
        def my_llm(prompt: str) -> str:
            return f"Response: {prompt}"

        config = LLMConfig(
            provider=LLMProvider.CUSTOM,
            custom_call_fn=my_llm
        )
        provider = CustomProvider(config)

        response = await provider.call("Hello")
        assert response.success is True
        assert "Hello" in response.content

    @pytest.mark.asyncio
    async def test_call_with_system_prompt(self):
        """测试带系统提示的调用"""
        def my_llm(prompt: str) -> str:
            return f"Processed: {prompt}"

        config = LLMConfig(
            provider=LLMProvider.CUSTOM,
            custom_call_fn=my_llm
        )
        provider = CustomProvider(config)

        response = await provider.call("Hello", system_prompt="You are a helper")
        assert response.success is True

    @pytest.mark.asyncio
    async def test_call_without_fn(self):
        """测试没有配置函数时"""
        config = LLMConfig(provider=LLMProvider.CUSTOM)
        provider = CustomProvider(config)

        response = await provider.call("Hello")
        assert response.success is False
        assert "未配置" in response.error

    @pytest.mark.asyncio
    async def test_is_available(self):
        """测试可用性检查"""
        config = LLMConfig(provider=LLMProvider.CUSTOM)
        provider = CustomProvider(config)
        assert await provider.is_available() is False

        config_with_fn = LLMConfig(
            provider=LLMProvider.CUSTOM,
            custom_call_fn=lambda x: x
        )
        provider_with_fn = CustomProvider(config_with_fn)
        assert await provider_with_fn.is_available() is True


class TestLLMFactory:
    """测试 LLM 工厂方法"""

    def test_create_claude_provider(self):
        """测试创建 Claude 提供商"""
        config = LLMConfig(provider=LLMProvider.CLAUDE)
        provider = create_llm_provider(config)
        assert isinstance(provider, ClaudeProvider)

    def test_create_openai_provider(self):
        """测试创建 OpenAI 提供商"""
        config = LLMConfig(provider=LLMProvider.OPENAI)
        provider = create_llm_provider(config)
        assert isinstance(provider, OpenAIProvider)

    def test_create_ollama_provider(self):
        """测试创建 Ollama 提供商"""
        config = LLMConfig(provider=LLMProvider.OLLAMA)
        provider = create_llm_provider(config)
        assert isinstance(provider, OllamaProvider)

    def test_create_custom_provider(self):
        """测试创建自定义提供商"""
        config = LLMConfig(
            provider=LLMProvider.CUSTOM,
            custom_call_fn=lambda x: x
        )
        provider = create_llm_provider(config)
        assert isinstance(provider, CustomProvider)

    def test_create_rule_provider_returns_none(self):
        """测试 RULE 提供商返回 None"""
        config = LLMConfig(provider=LLMProvider.RULE)
        provider = create_llm_provider(config)
        assert provider is None


class TestLLMConfig:
    """测试 LLMConfig 配置类"""

    def test_default_values(self):
        """测试默认值"""
        config = LLMConfig()
        assert config.provider == LLMProvider.RULE
        assert config.temperature == 0.7
        assert config.max_tokens == 1024
        assert config.timeout == 30

    def test_claude_config(self):
        """测试 Claude 配置"""
        config = LLMConfig(
            provider=LLMProvider.CLAUDE,
            claude_api_key="sk-test",
            claude_model="claude-3-opus"
        )
        assert config.provider == LLMProvider.CLAUDE
        assert config.claude_api_key == "sk-test"
        assert config.claude_model == "claude-3-opus"

    def test_openai_config(self):
        """测试 OpenAI 配置"""
        config = LLMConfig(
            provider=LLMProvider.OPENAI,
            openai_api_key="sk-openai",
            openai_model="gpt-4",
            openai_base_url="https://api.openai.com/v1"
        )
        assert config.provider == LLMProvider.OPENAI
        assert config.openai_api_key == "sk-openai"
        assert config.openai_model == "gpt-4"

    def test_ollama_config(self):
        """测试 Ollama 配置"""
        config = LLMConfig(
            provider=LLMProvider.OLLAMA,
            ollama_model="codellama",
            ollama_base_url="http://192.168.1.100:11434"
        )
        assert config.provider == LLMProvider.OLLAMA
        assert config.ollama_model == "codellama"
        assert config.ollama_base_url == "http://192.168.1.100:11434"


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