"""
Base Tool class and Tool registry for Agent tools
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, Any, List, Optional, Type


@dataclass
class ParameterValidation:
    """
    Parameter validation result

    Attributes:
        valid: Whether parameters are valid
        errors: List of validation error messages
    """

    valid: bool
    errors: List[str] = field(default_factory=list)


@dataclass
class ToolExecutionResult:
    """
    Tool execution result

    Attributes:
        success: Whether execution was successful
        error: Error message if failed
        data: Execution result data
        execution_time: Execution time in seconds
        metadata: Additional metadata
    """

    success: bool
    error: Optional[str]
    data: Any
    execution_time: Optional[float] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ToolInfo:
    """
    Tool information

    Attributes:
        name: Tool name
        description: Tool description
        parameters: Parameter schema
    """

    name: str
    description: str
    parameters: Dict[str, Any]


class ToolNotFoundError(Exception):
    """Exception raised when a tool is not found"""

    pass


class BaseTool(ABC):
    """
    Abstract base class for all Agent tools

    All tools must inherit from this class and implement the required methods.
    """

    def __init__(self, name: str, description: str):
        """
        Initialize the tool

        Args:
            name: Tool name
            description: Tool description
        """
        self.name = name
        self.description = description
        self.parameters_schema = self._define_parameters()

    @abstractmethod
    def _define_parameters(self) -> Dict[str, Any]:
        """
        Define the parameter schema for this tool

        Returns:
            Dictionary defining parameter schema with the following structure:
            {
                'param_name': {
                    'type': type,  # Python type (str, int, float, list, dict, etc.)
                    'required': bool,  # Whether parameter is required
                    'enum': List[Any],  # Optional: allowed values
                    'description': str  # Parameter description
                }
            }
        """
        pass

    @abstractmethod
    def execute(self, parameters: Dict[str, Any]) -> Any:
        """
        Execute the tool with given parameters

        Args:
            parameters: Tool parameters

        Returns:
            Tool execution result

        Raises:
            Exception: If execution fails
        """
        pass

    def validate_parameters(self, parameters: Dict[str, Any]) -> ParameterValidation:
        """
        Validate tool parameters against the schema

        Args:
            parameters: Parameters to validate

        Returns:
            ParameterValidation object with validation results
        """
        errors = []

        # Check required parameters
        required_params = [
            k for k, v in self.parameters_schema.items() if v.get("required", False)
        ]
        for param in required_params:
            if param not in parameters:
                errors.append(f"Missing required parameter: {param}")

        # Check parameter types
        for param, value in parameters.items():
            if param in self.parameters_schema:
                schema = self.parameters_schema[param]
                expected_type = schema.get("type")

                # Type checking
                if expected_type and not isinstance(value, expected_type):
                    errors.append(
                        f"Parameter '{param}' has wrong type: "
                        f"expected {expected_type.__name__}, got {type(value).__name__}"
                    )

                # Enum validation
                enum_values = schema.get("enum")
                if enum_values and value not in enum_values:
                    errors.append(
                        f"Parameter '{param}' has invalid value: "
                        f"must be one of {enum_values}, got '{value}'"
                    )

        return ParameterValidation(valid=len(errors) == 0, errors=errors)

    def get_info(self) -> ToolInfo:
        """
        Get tool information

        Returns:
            ToolInfo object with tool metadata
        """
        return ToolInfo(
            name=self.name,
            description=self.description,
            parameters=self.parameters_schema,
        )


class ToolRegistry:
    """
    Singleton registry for managing tool classes

    This registry allows tools to be registered and discovered at runtime.
    """

    _instance = None
    _tools: Dict[str, Type[BaseTool]] = {}

    def __new__(cls):
        """Ensure singleton pattern"""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    @classmethod
    def register(cls, tool_class: Type[BaseTool]) -> None:
        """
        Register a tool class

        Args:
            tool_class: Tool class to register
        """
        tool_name = tool_class.__name__
        cls._tools[tool_name] = tool_class

    @classmethod
    def get_tool_class(cls, tool_name: str) -> Type[BaseTool]:
        """
        Get a registered tool class by name

        Args:
            tool_name: Name of the tool class

        Returns:
            Tool class

        Raises:
            ToolNotFoundError: If tool is not registered
        """
        if tool_name not in cls._tools:
            raise ToolNotFoundError(f"Tool class '{tool_name}' not registered")
        return cls._tools[tool_name]

    @classmethod
    def list_registered_tools(cls) -> List[str]:
        """
        List all registered tool names

        Returns:
            List of registered tool class names
        """
        return list(cls._tools.keys())

    @classmethod
    def clear_registry(cls) -> None:
        """Clear all registered tools (mainly for testing)"""
        cls._tools.clear()


def register_tool(cls: Type[BaseTool]) -> Type[BaseTool]:
    """
    Decorator to register a tool class with the ToolRegistry

    Usage:
        @register_tool
        class MyTool(BaseTool):
            ...

    Args:
        cls: Tool class to register

    Returns:
        The same class (for decorator chaining)
    """
    ToolRegistry.register(cls)
    return cls
