"""
LLM 客户端集成测试

测试实际的 LLM 客户端功能，包括：
1. 请求格式验证
2. 工具 schema 转换
3. 错误处理
"""

import pytest
import asyncio
import json
from unittest.mock import AsyncMock, MagicMock, patch
from typing import List, Dict, Any

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))

from core.llm.base import (
    Message,
    MessageRole,
    ToolCall,
    ToolResult,
    ToolDefinition,
)
from core.llm.anthropic_client import AnthropicClient


class TestAnthropicSchemaValidation:
    """测试 Anthropic Schema 验证"""

    def test_pydantic_schema_to_anthropic_format(self):
        """测试 Pydantic Schema 转换为 Anthropic 格式"""
        from pydantic import BaseModel, Field

        class ReadFileArgs(BaseModel):
            file_path: str = Field(..., description="Absolute path to file")
            start_line: int = Field(1, description="Line number to start")
            end_line: int = Field(None, description="Line number to end")

        # 生成 Pydantic schema
        raw_schema = ReadFileArgs.model_json_schema()
        print(f"Raw Pydantic Schema:\n{json.dumps(raw_schema, indent=2)}")

        # 清理 schema：移除 title 字段
        clean_schema = {
            "type": raw_schema.get("type", "object"),
            "properties": raw_schema.get("properties", {}),
            "required": raw_schema.get("required", []),
        }

        print(f"\nClean Anthropic Schema:\n{json.dumps(clean_schema, indent=2)}")

        # 验证
        assert clean_schema["type"] == "object"
        assert "properties" in clean_schema
        assert "required" in clean_schema
        assert "title" not in clean_schema

    def test_anthropic_tool_definition(self):
        """测试 Anthropic 工具定义格式"""
        tool_definition = ToolDefinition(
            name="read_file",
            description="Read a file from the file system",
            parameters={
                "type": "object",
                "properties": {
                    "file_path": {
                        "type": "string",
                        "description": "Absolute path to the file",
                    },
                    "start_line": {
                        "type": "integer",
                        "description": "Line number to start reading from",
                        "default": 1,
                    },
                },
                "required": ["file_path"],
            },
        )

        # 转换为字典用于打印
        tool_dict = {
            "name": tool_definition.name,
            "description": tool_definition.description,
            "parameters": tool_definition.parameters,
        }
        print(f"Anthropic Tool Definition:\n{json.dumps(tool_dict, indent=2)}")

        # 验证格式
        assert tool_definition.name == "read_file"
        assert tool_definition.description == "Read a file from the file system"
        assert "input_schema" not in tool_definition.parameters
        assert tool_definition.parameters["type"] == "object"


class TestAnthropicClientValidation:
    """测试 Anthropic 客户端参数验证"""

    def test_client_initialization_validation(self):
        """测试客户端初始化验证"""
        # 测试缺少 model
        with pytest.raises(ValueError, match="Model name is required"):
            AnthropicClient(
                model="",
                api_key="test-key",
            )

        # 测试缺少 api_key
        with pytest.raises(ValueError, match="API key is required"):
            AnthropicClient(
                model="claude-3-5-sonnet-20241022",
                api_key="",
            )

        # 测试无效的 base_url
        with pytest.raises(ValueError, match="Invalid base_url format"):
            AnthropicClient(
                model="claude-3-5-sonnet-20241022",
                api_key="test-key",
                base_url="invalid-url",
            )

        # 测试有效的初始化
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
            base_url="https://api.anthropic.com",
        )
        assert client.model == "claude-3-5-sonnet-20241022"

    def test_chat_stream_parameter_validation(self):
        """测试 chat_stream 参数验证"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
        )

        messages = [
            Message(role=MessageRole.USER, content="Hello"),
        ]

        # 测试空消息列表
        async def test_empty_messages():
            with pytest.raises(ValueError, match="Messages list cannot be empty"):
                async for _ in client.chat_stream([]):
                    pass

        # 测试无效的 temperature
        async def test_invalid_temperature():
            with pytest.raises(
                ValueError, match="Temperature must be between 0.0 and 1.0"
            ):
                async for _ in client.chat_stream(messages, temperature=1.5):
                    pass

        # 测试无效的 max_tokens
        async def test_invalid_max_tokens():
            with pytest.raises(ValueError, match="max_tokens must be at least 1"):
                async for _ in client.chat_stream(messages, max_tokens=0):
                    pass

        # 运行测试
        asyncio.run(test_empty_messages())
        asyncio.run(test_invalid_temperature())
        asyncio.run(test_invalid_max_tokens())

    def test_chat_parameter_validation(self):
        """测试 chat 参数验证"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
        )

        messages = [
            Message(role=MessageRole.USER, content="Hello"),
        ]

        # 测试无效的参数
        async def test_invalid_params():
            with pytest.raises(
                ValueError, match="Temperature must be between 0.0 and 1.0"
            ):
                await client.chat(messages, temperature=-0.1)

        asyncio.run(test_invalid_params())


class TestAnthropicMessageFormat:
    """测试 Anthropic 消息格式"""

    def test_empty_assistant_message_handling(self):
        """测试空内容的助手消息"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
        )

        messages = [
            Message(role=MessageRole.USER, content="What's the weather?"),
            Message(
                role=MessageRole.ASSISTANT,
                content="",  # 空内容
                tool_calls=[
                    ToolCall(
                        id="call_123",
                        name="get_weather",
                        arguments={"location": "NYC"},
                    )
                ],
            ),
        ]

        system_prompt, anthropic_messages = client._convert_messages(messages)

        print(f"\nSystem Prompt: {system_prompt}")
        print(f"Anthropic Messages:\n{json.dumps(anthropic_messages, indent=2)}")

        # 验证格式
        assert len(anthropic_messages) == 2
        assert anthropic_messages[1]["role"] == "assistant"
        assert isinstance(anthropic_messages[1]["content"], list)
        assert anthropic_messages[1]["content"][0]["type"] == "tool_use"

    def test_tool_result_message_format(self):
        """测试工具结果消息格式"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
        )

        messages = [
            Message(
                role=MessageRole.TOOL,
                tool_result=ToolResult(
                    tool_call_id="call_123",
                    name="get_weather",
                    content="Sunny, 72°F",
                    is_error=False,
                ),
            ),
        ]

        system_prompt, anthropic_messages = client._convert_messages(messages)

        print(
            f"\nAnthropic Tool Result Message:\n{json.dumps(anthropic_messages, indent=2)}"
        )

        # 验证格式
        assert len(anthropic_messages) == 1
        assert anthropic_messages[0]["role"] == "user"
        assert isinstance(anthropic_messages[0]["content"], list)
        assert anthropic_messages[0]["content"][0]["type"] == "tool_result"
        assert anthropic_messages[0]["content"][0]["tool_use_id"] == "call_123"
        assert anthropic_messages[0]["content"][0]["is_error"] is False


class TestAnthropicToolDefinition:
    """测试 Anthropic 工具定义"""

    def test_tool_with_required_parameters(self):
        """测试带必需参数的工具"""
        from pydantic import BaseModel, Field

        class WriteFileArgs(BaseModel):
            file_path: str = Field(..., description="File path")
            content: str = Field(..., description="File content")

        raw_schema = WriteFileArgs.model_json_schema()

        # 清理 schema
        clean_schema = {
            "type": raw_schema.get("type", "object"),
            "properties": raw_schema.get("properties", {}),
            "required": raw_schema.get("required", []),
        }

        print(f"\nTool with Required Parameters:\n{json.dumps(clean_schema, indent=2)}")

        # 验证
        assert len(clean_schema["required"]) == 2
        assert "file_path" in clean_schema["required"]
        assert "content" in clean_schema["required"]

    def test_tool_with_optional_parameters(self):
        """测试带可选参数的工具"""
        from pydantic import BaseModel, Field

        class ReadFileArgs(BaseModel):
            file_path: str = Field(..., description="File path")
            start_line: int = Field(1, description="Start line")
            end_line: int = Field(None, description="End line")

        raw_schema = ReadFileArgs.model_json_schema()

        # 清理 schema
        clean_schema = {
            "type": raw_schema.get("type", "object"),
            "properties": raw_schema.get("properties", {}),
            "required": raw_schema.get("required", []),
        }

        print(f"\nTool with Optional Parameters:\n{json.dumps(clean_schema, indent=2)}")

        # 验证
        assert len(clean_schema["required"]) == 1
        assert "file_path" in clean_schema["required"]
        assert "start_line" not in clean_schema["required"]
        assert "end_line" not in clean_schema["required"]


class TestAnthropicErrorResponse:
    """测试 Anthropic 错误响应处理"""

    def test_error_message_format(self):
        """测试错误消息格式"""
        # 模拟 Anthropic API 错误响应
        error_response = {
            "type": "error",
            "error": {
                "type": "invalid_request_error",
                "message": "Improperly formed request.",
            },
        }

        print(f"\nAnthropic Error Response:\n{json.dumps(error_response, indent=2)}")

        # 验证格式
        assert "error" in error_response
        assert error_response["error"]["type"] == "invalid_request_error"
        assert error_response["error"]["message"] == "Improperly formed request."

    def test_tool_validation_error(self):
        """测试工具验证错误"""
        # 模拟工具验证错误
        error_response = {
            "type": "error",
            "error": {
                "type": "invalid_request_error",
                "message": "Invalid tool definition: missing 'input_schema' field.",
            },
        }

        print(f"\nTool Validation Error:\n{json.dumps(error_response, indent=2)}")

        # 验证
        assert "tool" in error_response["error"]["message"].lower()


class TestAnthropicRequestLogging:
    """测试 Anthropic 请求日志"""

    def test_request_log_format(self):
        """测试请求日志格式"""
        log_entry = {
            "type": "anthropic_stream_start",
            "model": "claude-3-5-sonnet-20241022",
            "message_count": 3,
            "has_tools": True,
        }

        print(f"\nRequest Log Entry:\n{json.dumps(log_entry, indent=2)}")

        # 验证
        assert log_entry["type"] == "anthropic_stream_start"
        assert log_entry["model"] == "claude-3-5-sonnet-20241022"
        assert log_entry["message_count"] == 3
        assert log_entry["has_tools"] is True

    def test_error_log_format(self):
        """测试错误日志格式"""
        import traceback

        try:
            # 模拟错误
            raise ValueError("Test error")
        except Exception as e:
            error_log = {
                "type": "anthropic_stream_error",
                "model": "claude-3-5-sonnet-20241022",
                "error": str(e),
                "error_type": type(e).__name__,
                "traceback": traceback.format_exc(),
                "request_params": {
                    "model": "claude-3-5-sonnet-20241022",
                    "message_count": 1,
                    "has_tools": False,
                    "temperature": 0.7,
                    "max_tokens": 4096,
                },
            }

            print(f"\nError Log Entry:\n{json.dumps(error_log, indent=2, default=str)}")

            # 验证
            assert error_log["error_type"] == "ValueError"
            assert "traceback" in error_log
            assert "request_params" in error_log


class TestAnthropicBaseURLHandling:
    """测试 base_url 处理"""

    def test_base_url_with_trailing_slash(self):
        """测试带尾部斜杠的 base_url"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
            base_url="https://api.anthropic.com/",
        )

        # base_url 应该保持原样（SDK 会处理）
        assert client.base_url == "https://api.anthropic.com/"

    def test_base_url_with_path(self):
        """测试带路径的 base_url"""
        client = AnthropicClient(
            model="claude-3-5-sonnet-20241022",
            api_key="test-key",
            base_url="https://api.anthropic.com/v1",
        )

        assert client.base_url == "https://api.anthropic.com/v1"

    def test_base_url_without_protocol(self):
        """测试不带协议的 base_url（应该失败）"""
        with pytest.raises(ValueError, match="Invalid base_url format"):
            AnthropicClient(
                model="claude-3-5-sonnet-20241022",
                api_key="test-key",
                base_url="api.anthropic.com",
            )


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