"""Unit tests for tool builder.

This module contains comprehensive tests for the tool function builder,
including type conversion and dynamic function generation.
"""

from unittest.mock import patch

import pytest

from hezor_common.data_model.searching.data.api_models import (
    ExecuteResponse,
    ParameterPropertySchema,
    ParameterSchema,
    SearchResponse,
    ToolSchema,
)
from hezor_common.transfer.base_sdk import MetaInfo
from hezor_common.transfer.datahub_sdk.tool_builder import (
    _convert_json_type_to_python,
    build_tool_functions,
)


@pytest.fixture
def mock_meta_info():
    """Create a mock MetaInfo object."""
    return MetaInfo(
        subject="测试主体",
        subject_code="test_001",
        caller_id="user_123",
        data_coverage="202401-202412",
        creation_slug="test_model",
        creation_name="测试模型",
    )


@pytest.fixture
def sample_tool_schema():
    """Create a sample ToolSchema for testing."""
    param_schema = ParameterSchema(
        type="object",
        desc="Tool parameters",
        props={
            "city": ParameterPropertySchema(type="string", desc="City name"),
            "date": ParameterPropertySchema(type="string", desc="Date"),
            "temperature": ParameterPropertySchema(type="number", desc="Temperature"),
        },
        required=["city"],
    )

    return ToolSchema(
        name="get_weather",
        desc="Get weather information",
        params=param_schema,
        returns="Weather data",
    )


@pytest.fixture
def sample_search_response(sample_tool_schema):
    """Create a sample SearchResponse."""
    return SearchResponse(tools=[sample_tool_schema])


@pytest.fixture
def sample_execute_response():
    """Create a sample ExecuteResponse."""
    return ExecuteResponse(
        success=True,
        data={"temperature": 25, "condition": "sunny"},
        count=1,
    )


class TestConvertJsonTypeToPython:
    """Tests for _convert_json_type_to_python helper function."""

    def test_convert_string(self):
        """Test conversion of string type."""
        assert _convert_json_type_to_python("string") is str

    def test_convert_integer(self):
        """Test conversion of integer type."""
        assert _convert_json_type_to_python("integer") is int

    def test_convert_number(self):
        """Test conversion of number type."""
        assert _convert_json_type_to_python("number") is float

    def test_convert_boolean(self):
        """Test conversion of boolean type."""
        assert _convert_json_type_to_python("boolean") is bool

    def test_convert_array(self):
        """Test conversion of array type."""
        assert _convert_json_type_to_python("array") is list

    def test_convert_object(self):
        """Test conversion of object type."""
        assert _convert_json_type_to_python("object") is dict

    def test_convert_unknown_defaults_to_string(self):
        """Test that unknown types default to string."""
        assert _convert_json_type_to_python("unknown_type") is str


class TestBuildToolFunctions:
    """Tests for build_tool_functions."""

    def test_build_from_search_response(self, sample_search_response):
        """Test building tool functions from SearchResponse."""
        tool_functions = build_tool_functions(sample_search_response)

        assert "get_weather" in tool_functions
        assert callable(tool_functions["get_weather"])

    def test_build_from_tool_list(self, sample_tool_schema):
        """Test building tool functions from tool list."""
        tool_functions = build_tool_functions([sample_tool_schema])

        assert "get_weather" in tool_functions
        assert callable(tool_functions["get_weather"])

    def test_built_function_has_correct_name(self, sample_tool_schema):
        """Test that built function has correct name."""
        tool_functions = build_tool_functions([sample_tool_schema])
        func = tool_functions["get_weather"]

        assert func.__name__ == "get_weather"

    def test_built_function_has_docstring(self, sample_tool_schema):
        """Test that built function has proper docstring."""
        tool_functions = build_tool_functions([sample_tool_schema])
        func = tool_functions["get_weather"]

        assert func.__doc__ is not None
        assert "Get weather information" in func.__doc__
        assert "Parameters" in func.__doc__
        assert "city" in func.__doc__

    def test_built_function_has_signature(self, sample_tool_schema):
        """Test that built function has proper signature."""
        import inspect

        tool_functions = build_tool_functions([sample_tool_schema])
        func = tool_functions["get_weather"]

        sig = inspect.signature(func)
        assert "city" in sig.parameters
        assert "date" in sig.parameters
        assert "temperature" in sig.parameters

        # city is required (no default)
        assert sig.parameters["city"].default == inspect.Parameter.empty
        # date and temperature are optional (have default None)
        assert sig.parameters["date"].default is None
        assert sig.parameters["temperature"].default is None

    @pytest.mark.asyncio
    async def test_built_function_filters_reserved_params(
        self, sample_tool_schema, sample_execute_response
    ):
        """Test that built function filters out reserved parameters."""
        with patch("hezor_common.transfer.datahub_sdk.execute_tool.execute_tool") as mock_execute:
            mock_execute.return_value = sample_execute_response

            tool_functions = build_tool_functions(
                [sample_tool_schema],
                api_key="configured-key",
            )
            func = tool_functions["get_weather"]

            # Call with business params and accidentally pass reserved params
            await func(
                city="北京",
                api_key="wrong-key",  # Should be filtered
                base_url="http://wrong.url",  # Should be filtered
            )

            # Verify execute_tool was called with filtered args
            mock_execute.assert_called_once()
            call_args = mock_execute.call_args

            # Args should only contain business parameter
            assert call_args.kwargs["args"] == {"city": "北京"}
            # API key should use configured value
            assert call_args.kwargs["api_key"] == "configured-key"

    @pytest.mark.asyncio
    async def test_built_function_executes_correctly(
        self, sample_tool_schema, sample_execute_response
    ):
        """Test that built function executes tool correctly."""
        with patch("hezor_common.transfer.datahub_sdk.execute_tool.execute_tool") as mock_execute:
            mock_execute.return_value = sample_execute_response

            tool_functions = build_tool_functions([sample_tool_schema])
            func = tool_functions["get_weather"]

            result = await func(city="北京", date="2024-01-01")

            # Should return data from ExecuteResponse
            assert result == {"temperature": 25, "condition": "sunny"}

            # Verify execute_tool was called correctly
            mock_execute.assert_called_once()
            call_kwargs = mock_execute.call_args.kwargs
            assert call_kwargs["tool_name"] == "get_weather"
            assert call_kwargs["args"] == {"city": "北京", "date": "2024-01-01"}

    @pytest.mark.asyncio
    async def test_built_function_with_authentication(
        self, sample_tool_schema, sample_execute_response, mock_meta_info
    ):
        """Test that built function uses configured authentication."""
        with patch("hezor_common.transfer.datahub_sdk.execute_tool.execute_tool") as mock_execute:
            mock_execute.return_value = sample_execute_response

            tool_functions = build_tool_functions(
                [sample_tool_schema],
                api_key="test-key",
                meta_info=mock_meta_info,
                private_key_path="test.pem",
                password=b"password",
                meta_info_expires_in=7200,
            )
            func = tool_functions["get_weather"]

            await func(city="北京")

            # Verify authentication parameters were passed through
            call_kwargs = mock_execute.call_args.kwargs
            assert call_kwargs["api_key"] == "test-key"
            assert call_kwargs["meta_info"] == mock_meta_info
            assert call_kwargs["private_key_path"] == "test.pem"
            assert call_kwargs["password"] == b"password"
            assert call_kwargs["meta_info_expires_in"] == 7200

    def test_built_function_parameter_types(self, sample_tool_schema):
        """Test that built function has correct parameter type annotations."""
        import inspect

        tool_functions = build_tool_functions([sample_tool_schema])
        func = tool_functions["get_weather"]

        sig = inspect.signature(func)

        # city is required string
        city_param = sig.parameters["city"]
        assert city_param.annotation is str

        # date is optional string
        date_param = sig.parameters["date"]
        # Optional type will be str | None
        assert hasattr(date_param.annotation, "__args__")

    def test_build_multiple_tools(self):
        """Test building functions for multiple tools."""
        tool1 = ToolSchema(
            name="tool1",
            desc="First tool",
            params=ParameterSchema(type="object", desc="Params"),
            returns="Result",
        )
        tool2 = ToolSchema(
            name="tool2",
            desc="Second tool",
            params=ParameterSchema(type="object", desc="Params"),
            returns="Result",
        )

        tool_functions = build_tool_functions([tool1, tool2])

        assert len(tool_functions) == 2
        assert "tool1" in tool_functions
        assert "tool2" in tool_functions
