"""
Unit tests for AgentTool and ToolRegistry.

Tests the core tool abstraction system including:
- AgentTool creation and execution
- ToolRegistry management
- Tool format conversion
- Custom tool creation from functions
"""

import pytest
import asyncio
from typing import Dict, Any

from daita.core.tools import AgentTool, ToolRegistry


class TestAgentTool:
    """Test AgentTool class functionality"""

    def test_agenttool_creation(self):
        """Test basic AgentTool creation"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        tool = AgentTool(
            name="test_tool",
            description="A test tool",
            parameters={
                "param1": {
                    "type": "string",
                    "description": "Test parameter",
                    "required": True
                }
            },
            handler=test_handler,
            category="test",
            source="custom"
        )

        assert tool.name == "test_tool"
        assert tool.description == "A test tool"
        assert tool.category == "test"
        assert tool.source == "custom"
        assert "param1" in tool.parameters

    def test_to_llm_function(self):
        """Test LLM function format conversion"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        tool = AgentTool(
            name="test_tool",
            description="A test tool",
            parameters={
                "param1": {
                    "type": "string",
                    "description": "Test parameter",
                    "required": True
                },
                "param2": {
                    "type": "integer",
                    "description": "Optional parameter",
                    "required": False
                }
            },
            handler=test_handler
        )

        llm_format = tool.to_llm_function()

        assert llm_format["name"] == "test_tool"
        assert llm_format["description"] == "A test tool"
        assert "parameters" in llm_format
        assert llm_format["parameters"]["type"] == "object"
        assert "param1" in llm_format["parameters"]["properties"]
        assert "param1" in llm_format["parameters"]["required"]
        assert "param2" not in llm_format["parameters"]["required"]

    def test_to_openai_function(self):
        """Test OpenAI function format conversion"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        tool = AgentTool(
            name="test_tool",
            description="A test tool",
            parameters={
                "param1": {
                    "type": "string",
                    "description": "Test parameter",
                    "required": True
                }
            },
            handler=test_handler
        )

        openai_format = tool.to_openai_function()

        assert openai_format["type"] == "function"
        assert "function" in openai_format
        assert openai_format["function"]["name"] == "test_tool"
        assert "parameters" in openai_format["function"]

    def test_to_anthropic_tool(self):
        """Test Anthropic tool format conversion"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        tool = AgentTool(
            name="test_tool",
            description="A test tool",
            parameters={
                "param1": {
                    "type": "string",
                    "description": "Test parameter",
                    "required": True
                }
            },
            handler=test_handler
        )

        anthropic_format = tool.to_anthropic_tool()

        assert anthropic_format["name"] == "test_tool"
        assert anthropic_format["description"] == "A test tool"
        assert "input_schema" in anthropic_format
        assert anthropic_format["input_schema"]["type"] == "object"

    def test_to_prompt_description(self):
        """Test prompt description generation"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        tool = AgentTool(
            name="test_tool",
            description="A test tool for testing",
            parameters={
                "param1": {
                    "type": "string",
                    "description": "First parameter",
                    "required": True
                },
                "param2": {
                    "type": "integer",
                    "description": "Second parameter",
                    "required": False
                }
            },
            handler=test_handler
        )

        description = tool.to_prompt_description()

        assert "test_tool" in description
        assert "A test tool for testing" in description
        assert "param1" in description
        assert "param2" in description
        assert "(required)" in description
        assert "(optional)" in description

    @pytest.mark.asyncio
    async def test_tool_execution(self):
        """Test tool execution"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": args.get("input") * 2}

        tool = AgentTool(
            name="double",
            description="Double the input",
            parameters={"input": {"type": "integer", "required": True}},
            handler=test_handler
        )

        result = await tool.execute({"input": 5})
        assert result["result"] == 10

    @pytest.mark.asyncio
    async def test_tool_execution_with_timeout(self):
        """Test tool execution with timeout"""
        async def slow_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            await asyncio.sleep(2)
            return {"result": "done"}

        tool = AgentTool(
            name="slow_tool",
            description="A slow tool",
            parameters={},
            handler=slow_handler,
            timeout_seconds=1
        )

        with pytest.raises(RuntimeError, match="timed out"):
            await tool.execute({})

    def test_from_function_sync(self):
        """Test creating AgentTool from sync function"""
        def sync_function(param1: str, param2: int = 10):
            """Test sync function"""
            return f"{param1}_{param2}"

        tool = AgentTool.from_function(sync_function)

        assert tool.name == "sync_function"
        assert "Test sync function" in tool.description
        assert "param1" in tool.parameters
        assert "param2" in tool.parameters
        assert tool.parameters["param1"]["required"] == True
        assert tool.parameters["param2"]["required"] == False

    def test_from_function_async(self):
        """Test creating AgentTool from async function"""
        async def async_function(query: str):
            """Search for something"""
            return ["result1", "result2"]

        tool = AgentTool.from_function(async_function)

        assert tool.name == "async_function"
        assert "Search for something" in tool.description
        assert "query" in tool.parameters
        assert tool.parameters["query"]["type"] == "string"

    def test_from_function_with_explicit_params(self):
        """Test creating AgentTool with explicit parameters"""
        async def my_function(x, y):
            return x + y

        tool = AgentTool.from_function(
            my_function,
            name="add_numbers",
            description="Add two numbers",
            parameters={
                "x": {"type": "integer", "description": "First number", "required": True},
                "y": {"type": "integer", "description": "Second number", "required": True}
            },
            category="math"
        )

        assert tool.name == "add_numbers"
        assert tool.description == "Add two numbers"
        assert tool.category == "math"
        assert tool.parameters["x"]["type"] == "integer"


class TestToolRegistry:
    """Test ToolRegistry class functionality"""

    def test_registry_creation(self):
        """Test ToolRegistry creation"""
        registry = ToolRegistry()
        assert registry.tool_count == 0
        assert len(registry.tools) == 0

    def test_register_tool(self):
        """Test registering a single tool"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": "success"}

        registry = ToolRegistry()
        tool = AgentTool(
            name="test_tool",
            description="Test",
            parameters={},
            handler=test_handler
        )

        registry.register(tool)

        assert registry.tool_count == 1
        assert "test_tool" in registry.tool_names
        assert registry.get("test_tool") == tool

    def test_register_many_tools(self):
        """Test registering multiple tools"""
        async def handler1(args): return {"result": 1}
        async def handler2(args): return {"result": 2}

        registry = ToolRegistry()
        tools = [
            AgentTool(name="tool1", description="Tool 1", parameters={}, handler=handler1),
            AgentTool(name="tool2", description="Tool 2", parameters={}, handler=handler2)
        ]

        registry.register_many(tools)

        assert registry.tool_count == 2
        assert "tool1" in registry.tool_names
        assert "tool2" in registry.tool_names

    def test_register_duplicate_tool_warning(self, caplog):
        """Test that registering duplicate tool name shows warning"""
        async def handler1(args): return {"result": 1}
        async def handler2(args): return {"result": 2}

        registry = ToolRegistry()
        tool1 = AgentTool(name="duplicate", description="First", parameters={}, handler=handler1)
        tool2 = AgentTool(name="duplicate", description="Second", parameters={}, handler=handler2)

        registry.register(tool1)
        registry.register(tool2)

        assert registry.tool_count == 2  # Both added to list
        assert registry.get("duplicate") == tool2  # Map has second one

    def test_get_nonexistent_tool(self):
        """Test getting a tool that doesn't exist"""
        registry = ToolRegistry()
        assert registry.get("nonexistent") is None

    @pytest.mark.asyncio
    async def test_execute_tool(self):
        """Test executing a tool via registry"""
        async def test_handler(args: Dict[str, Any]) -> Dict[str, Any]:
            return {"result": args.get("value") * 3}

        registry = ToolRegistry()
        tool = AgentTool(
            name="triple",
            description="Triple the value",
            parameters={"value": {"type": "integer", "required": True}},
            handler=test_handler
        )

        registry.register(tool)
        result = await registry.execute("triple", {"value": 7})

        assert result["result"] == 21

    @pytest.mark.asyncio
    async def test_execute_nonexistent_tool(self):
        """Test executing a tool that doesn't exist"""
        registry = ToolRegistry()

        with pytest.raises(RuntimeError, match="not found"):
            await registry.execute("nonexistent", {})

    def test_tool_names_property(self):
        """Test tool_names property"""
        async def handler(args): return {}

        registry = ToolRegistry()
        registry.register(AgentTool(name="tool1", description="", parameters={}, handler=handler))
        registry.register(AgentTool(name="tool2", description="", parameters={}, handler=handler))

        names = registry.tool_names
        assert len(names) == 2
        assert "tool1" in names
        assert "tool2" in names


class TestAgentToolIntegration:
    """Integration tests for AgentTool features"""

    @pytest.mark.asyncio
    async def test_tool_with_category(self):
        """Test tool with category metadata"""
        async def db_query(args): return {"rows": []}

        tool = AgentTool(
            name="query_db",
            description="Query database",
            parameters={},
            handler=db_query,
            category="database",
            source="plugin"
        )

        assert tool.category == "database"
        assert tool.source == "plugin"

    @pytest.mark.asyncio
    async def test_complete_tool_workflow(self):
        """Test complete workflow: create, register, execute"""
        # Create a simple tool
        async def calculator(args: Dict[str, Any]) -> Dict[str, Any]:
            operation = args.get("operation")
            a = args.get("a")
            b = args.get("b")

            if operation == "add":
                return {"result": a + b}
            elif operation == "multiply":
                return {"result": a * b}
            else:
                return {"error": "Unknown operation"}

        tool = AgentTool(
            name="calculator",
            description="Perform calculations",
            parameters={
                "operation": {"type": "string", "required": True},
                "a": {"type": "number", "required": True},
                "b": {"type": "number", "required": True}
            },
            handler=calculator,
            category="math"
        )

        # Register in registry
        registry = ToolRegistry()
        registry.register(tool)

        # Execute via registry
        result = await registry.execute("calculator", {
            "operation": "add",
            "a": 10,
            "b": 5
        })

        assert result["result"] == 15

        # Test another operation
        result = await registry.execute("calculator", {
            "operation": "multiply",
            "a": 10,
            "b": 5
        })

        assert result["result"] == 50
