"""Tool gateway for registering and executing tools.

This module provides the gateway for managing tools that can be
called by AI agents.
"""

import logging
from typing import Any, Callable, Dict, List, Optional, Union

from agent_factory.models.base import ToolDefinition, ToolUse
from agent_factory.tools.base import BaseTool, FunctionTool, ToolResult

logger = logging.getLogger(__name__)


class ToolGateway:
    """Gateway for registering and executing tools.

    Manages a collection of tools that can be called by AI models
    and handles tool execution.

    Example:
        gateway = ToolGateway()

        # Register a tool class
        gateway.register(WeatherTool())

        # Register a function as a tool
        @gateway.register_function(name="calculator", description="Do math")
        def calculate(expression: str) -> float:
            return eval(expression)

        # Get tool definitions for AI model
        tools = gateway.get_tool_definitions()

        # Execute a tool
        result = gateway.execute(tool_use)
    """

    def __init__(self):
        """Initialize the tool gateway."""
        self._tools: Dict[str, BaseTool] = {}
        self._executors: Dict[str, Callable] = {}

    def register(self, tool: BaseTool) -> None:
        """Register a tool.

        Args:
            tool: Tool instance to register
        """
        if not tool.name:
            raise ValueError("Tool must have a name")

        self._tools[tool.name] = tool
        self._executors[tool.name] = tool.execute

        logger.debug(f"Registered tool: {tool.name}")

    def register_function(
        self,
        name: str,
        description: str,
        input_schema: Optional[Dict[str, Any]] = None,
    ) -> Callable[[Callable], Callable]:
        """Decorator to register a function as a tool.

        Args:
            name: Tool name
            description: Tool description
            input_schema: Optional JSON schema for inputs

        Returns:
            Decorator function
        """

        def decorator(func: Callable) -> Callable:
            tool = FunctionTool(func=func, name=name, description=description)
            if input_schema:
                tool._schema = input_schema
            self.register(tool)
            return func

        return decorator

    def register_tool(
        self,
        name: str,
        description: str,
        input_schema: Dict[str, Any],
        executor: Callable[[Dict[str, Any]], Any],
    ) -> None:
        """Register a tool with explicit definition.

        Args:
            name: Tool name
            description: Tool description
            input_schema: JSON schema for inputs
            executor: Function to execute the tool
        """

        class DynamicTool(BaseTool):
            pass

        tool = DynamicTool()
        tool.name = name
        tool.description = description
        tool._input_schema = input_schema
        tool.input_schema = property(lambda self: self._input_schema)  # type: ignore

        self._tools[name] = tool
        self._executors[name] = executor

        logger.debug(f"Registered dynamic tool: {name}")

    def get_tool_definitions(self) -> List[ToolDefinition]:
        """Get all tool definitions for AI model.

        Returns:
            List of ToolDefinition objects
        """
        definitions = []
        for tool in self._tools.values():
            definitions.append(
                ToolDefinition(
                    name=tool.name,
                    description=tool.description,
                    input_schema=tool.input_schema,
                )
            )
        return definitions

    def get_tools_for_bedrock(self) -> List[Dict[str, Any]]:
        """Get tools formatted for Bedrock Converse API.

        Returns:
            List of tool specifications in Bedrock format
        """
        return [defn.to_bedrock_format() for defn in self.get_tool_definitions()]

    def execute(
        self,
        tool_use: Union[ToolUse, Dict[str, Any]],
        context: Optional[Dict[str, Any]] = None,
    ) -> ToolResult:
        """Execute a tool.

        Args:
            tool_use: Tool use request from the model
            context: Optional context to inject into tool inputs

        Returns:
            ToolResult with execution result
        """
        # Normalize tool_use
        if isinstance(tool_use, dict):
            tool_use_id = tool_use.get("toolUseId", tool_use.get("tool_use_id", ""))
            name = tool_use.get("name", "")
            inputs = tool_use.get("input", {})
        else:
            tool_use_id = tool_use.tool_use_id
            name = tool_use.name
            inputs = tool_use.input

        # Inject context
        if context:
            inputs = {**inputs, **context}

        logger.info(f"Executing tool: {name}")

        # Get executor
        executor = self._executors.get(name)
        if not executor:
            logger.error(f"Tool not found: {name}")
            return ToolResult(
                tool_use_id=tool_use_id,
                content={"error": f"Tool not found: {name}"},
                is_error=True,
            )

        try:
            result = executor(**inputs) if callable(executor) else executor(inputs)

            # Ensure result is serializable
            if not isinstance(result, (str, dict, list, int, float, bool, type(None))):
                result = str(result)

            return ToolResult(
                tool_use_id=tool_use_id,
                content=result,
                is_error=False,
            )

        except Exception as e:
            logger.exception(f"Tool execution failed: {name}")
            return ToolResult(
                tool_use_id=tool_use_id,
                content={"error": str(e)},
                is_error=True,
            )

    def has_tool(self, name: str) -> bool:
        """Check if a tool is registered.

        Args:
            name: Tool name

        Returns:
            True if tool exists
        """
        return name in self._tools

    def list_tools(self) -> List[str]:
        """List all registered tool names.

        Returns:
            List of tool names
        """
        return list(self._tools.keys())

    def clear(self) -> None:
        """Remove all registered tools."""
        self._tools.clear()
        self._executors.clear()
