"""Unit tests for tool decorator and CallableTool.

This module tests the @tool() decorator and related tool functionality.
"""

from __future__ import annotations

import pytest

from agentlite.tool import tool, CallableTool, ToolOk, ToolError


class TestToolDecorator:
    """Tests for the @tool() decorator."""

    def test_tool_decorator_basic(self):
        """Test basic tool decorator functionality."""

        @tool()
        async def add(a: float, b: float) -> float:
            """Add two numbers."""
            return a + b

        assert isinstance(add, CallableTool)
        assert add.name == "add"
        assert add.description == "Add two numbers."
        assert add.parameters["type"] == "object"
        assert "a" in add.parameters["properties"]
        assert "b" in add.parameters["properties"]
        assert add.parameters["properties"]["a"]["type"] == "number"
        assert add.parameters["properties"]["b"]["type"] == "number"
        assert add.parameters["required"] == ["a", "b"]

    def test_tool_decorator_with_default_params(self):
        """Test tool decorator with default parameters."""

        @tool()
        async def greet(name: str, greeting: str = "Hello") -> str:
            """Greet someone."""
            return f"{greeting}, {name}!"

        assert greet.name == "greet"
        assert "name" in greet.parameters["required"]
        assert "greeting" not in greet.parameters["required"]

    def test_tool_decorator_custom_name(self):
        """Test tool decorator with custom name."""

        @tool(name="custom_add")
        async def add(a: float, b: float) -> float:
            """Add two numbers."""
            return a + b

        assert add.name == "custom_add"

    def test_tool_decorator_custom_description(self):
        """Test tool decorator with custom description."""

        @tool(description="Custom description")
        async def add(a: float, b: float) -> float:
            """Add two numbers."""
            return a + b

        assert add.description == "Custom description"

    def test_tool_decorator_no_docstring(self):
        """Test tool decorator with no docstring."""

        @tool()
        async def no_doc(a: float) -> float:
            return a

        assert no_doc.description == "No description provided"

    def test_tool_decorator_param_types(self):
        """Test tool decorator with various parameter types."""

        @tool()
        async def multi_types(
            s: str,
            i: int,
            f: float,
            b: bool,
        ) -> dict:
            """Multiple types."""
            return {"s": s, "i": i, "f": f, "b": b}

        props = multi_types.parameters["properties"]
        assert props["s"]["type"] == "string"
        assert props["i"]["type"] == "integer"
        assert props["f"]["type"] == "number"
        assert props["b"]["type"] == "boolean"

    def test_tool_decorator_no_type_hints(self):
        """Test tool decorator with no type hints."""

        @tool()
        async def no_types(param) -> str:
            """No type hints."""
            return str(param)

        assert no_types.parameters["properties"]["param"]["type"] == "string"


class TestToolDecoratorExecution:
    """Tests for tool decorator execution."""

    @pytest.mark.asyncio
    async def test_tool_execution_success(self):
        """Test successful tool execution."""

        @tool()
        async def add(a: float, b: float) -> float:
            """Add two numbers."""
            return a + b

        result = await add(1.0, 2.0)
        assert isinstance(result, ToolOk)
        assert result.output == "3.0"

    @pytest.mark.asyncio
    async def test_tool_execution_error(self):
        """Test tool execution with error."""

        @tool()
        async def divide(a: float, b: float) -> float:
            """Divide two numbers."""
            return a / b

        result = await divide(1.0, 0.0)
        assert isinstance(result, ToolError)
        assert "division by zero" in result.message

    @pytest.mark.asyncio
    async def test_tool_execution_with_kwargs(self):
        """Test tool execution with keyword arguments."""

        @tool()
        async def greet(name: str, greeting: str = "Hello") -> str:
            """Greet someone."""
            return f"{greeting}, {name}!"

        result = await greet(name="World", greeting="Hi")
        assert isinstance(result, ToolOk)
        assert result.output == "Hi, World!"


class TestToolDecoratorMemorixBug:
    """Tests for the specific bug reported by Memorix project."""

    def test_tool_decorator_memorix_case(self):
        """Test the exact case from Memorix bug report.

        This test verifies that the @tool() decorator works correctly
        with async functions that have string and float parameters.
        """

        @tool()
        async def add_memory(content: str, importance: float = 0.5) -> dict:
            """存储记忆"""
            return {"status": "ok"}

        assert isinstance(add_memory, CallableTool)
        assert add_memory.name == "add_memory"
        assert add_memory.description == "存储记忆"

        # Check parameters schema
        params = add_memory.parameters
        assert params["type"] == "object"
        assert "content" in params["properties"]
        assert "importance" in params["properties"]
        assert params["properties"]["content"]["type"] == "string"
        assert params["properties"]["importance"]["type"] == "number"

        # content is required (no default), importance is optional
        assert "content" in params["required"]
        assert "importance" not in params["required"]

    @pytest.mark.asyncio
    async def test_tool_decorator_memorix_execution(self):
        """Test execution of the Memorix case."""

        @tool()
        async def add_memory(content: str, importance: float = 0.5) -> dict:
            """存储记忆"""
            return {"status": "ok", "content": content, "importance": importance}

        result = await add_memory("test content", 0.8)
        assert isinstance(result, ToolOk)
        assert "ok" in result.output

    def test_tool_decorator_can_be_used_in_agent(self):
        """Test that decorated tools can be used with Agent.

        This is an integration-style test to ensure the decorated tool
        has all required attributes for Agent usage.
        """

        @tool()
        async def add_memory(content: str, importance: float = 0.5) -> dict:
            """存储记忆"""
            return {"status": "ok"}

        # Verify the tool has the base property required by Agent
        assert hasattr(add_memory, "base")
        base_tool = add_memory.base
        assert base_tool.name == "add_memory"
        assert base_tool.description == "存储记忆"
        assert base_tool.parameters == add_memory.parameters
