"""Base tool interface for AI agent tools.

This module defines the interface for tools that can be registered
with an agent and called by the AI model.
"""

import inspect
import json
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, get_type_hints


# Maximum characters for tool result content to avoid context overflow
MAX_TOOL_RESULT_LENGTH = 50000  # ~50k chars, roughly ~12k tokens


@dataclass
class ToolResult:
    """Result from a tool execution."""

    tool_use_id: str
    content: Any
    is_error: bool = False

    def to_bedrock_format(self, max_length: int = MAX_TOOL_RESULT_LENGTH) -> Dict[str, Any]:
        """Convert to Bedrock Converse API format.

        Args:
            max_length: Maximum characters for content. Larger results are truncated.
        """
        content_text = (
            json.dumps(self.content) if not isinstance(self.content, str) else self.content
        )

        # Truncate if too large to avoid context overflow
        if len(content_text) > max_length:
            truncated_msg = f"\n\n[TRUNCATED: Result was {len(content_text):,} chars, showing first {max_length:,}]"
            content_text = content_text[: max_length - len(truncated_msg)] + truncated_msg

        result: Dict[str, Any] = {
            "toolUseId": self.tool_use_id,
            "content": [{"text": content_text}],
        }
        if self.is_error:
            result["status"] = "error"
        return result


class BaseTool(ABC):
    """Abstract base class for tools.

    Inherit from this class to create tools that can be used by AI agents.

    Example:
        class WeatherTool(BaseTool):
            name = "get_weather"
            description = "Get current weather for a city"

            @property
            def input_schema(self):
                return {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "City name"}
                    },
                    "required": ["city"]
                }

            def execute(self, city: str) -> Dict[str, Any]:
                return {"temperature": 72, "conditions": "sunny"}
    """

    name: str = ""
    description: str = ""

    @property
    @abstractmethod
    def input_schema(self) -> Dict[str, Any]:
        """JSON schema for the tool's input parameters.

        Returns:
            JSON Schema dictionary describing the input
        """
        pass

    @abstractmethod
    def execute(self, **kwargs) -> Any:
        """Execute the tool with the given parameters.

        Args:
            **kwargs: Tool parameters matching the input schema

        Returns:
            Tool result (will be serialized to JSON)

        Raises:
            Exception: If tool execution fails
        """
        pass

    def to_definition(self) -> Dict[str, Any]:
        """Convert to tool definition format."""
        from agent_factory.models.base import ToolDefinition

        return ToolDefinition(
            name=self.name,
            description=self.description,
            input_schema=self.input_schema,
        )


T = TypeVar("T")


def tool(
    name: Optional[str] = None,
    description: Optional[str] = None,
) -> Callable[[Callable[..., T]], "FunctionTool[T]"]:
    """Decorator to convert a function into a tool.

    Example:
        @tool(name="get_weather", description="Get weather for a city")
        def get_weather(city: str, unit: str = "celsius") -> dict:
            '''
            Get the current weather for a city.

            Args:
                city: The city name
                unit: Temperature unit (celsius or fahrenheit)

            Returns:
                Weather data
            '''
            return {"temperature": 22, "unit": unit}

        # Or with docstring-based documentation:
        @tool()
        def get_weather(city: str) -> dict:
            '''Get the current weather for a city.'''
            return {"temperature": 22}
    """

    def decorator(func: Callable[..., T]) -> "FunctionTool[T]":
        return FunctionTool(
            func=func,
            name=name or func.__name__,
            description=description or (func.__doc__ or "").split("\n")[0].strip(),
        )

    return decorator


class FunctionTool(BaseTool):
    """Tool wrapper for functions."""

    def __init__(
        self,
        func: Callable,
        name: str,
        description: str,
    ):
        self._func = func
        self.name = name
        self.description = description
        self._schema: Optional[Dict[str, Any]] = None

    @property
    def input_schema(self) -> Dict[str, Any]:
        """Generate JSON schema from function signature."""
        if self._schema is not None:
            return self._schema

        sig = inspect.signature(self._func)
        hints = get_type_hints(self._func) if hasattr(self._func, "__annotations__") else {}

        properties = {}
        required = []

        for param_name, param in sig.parameters.items():
            if param_name in ("self", "cls"):
                continue

            param_type = hints.get(param_name, str)
            json_type = self._python_type_to_json(param_type)

            properties[param_name] = {
                "type": json_type,
                "description": self._get_param_description(param_name),
            }

            if param.default is inspect.Parameter.empty:
                required.append(param_name)

        self._schema = {
            "type": "object",
            "properties": properties,
            "required": required,
        }

        return self._schema

    def _python_type_to_json(self, python_type: Type) -> str:
        """Convert Python type to JSON schema type."""
        type_map = {
            str: "string",
            int: "integer",
            float: "number",
            bool: "boolean",
            list: "array",
            dict: "object",
            List: "array",
            Dict: "object",
        }

        # Handle generic types
        origin = getattr(python_type, "__origin__", None)
        if origin is not None:
            return type_map.get(origin, "string")

        return type_map.get(python_type, "string")

    def _get_param_description(self, param_name: str) -> str:
        """Extract parameter description from docstring."""
        docstring = self._func.__doc__ or ""

        # Look for Args section
        lines = docstring.split("\n")
        in_args = False

        for line in lines:
            stripped = line.strip()
            if stripped.lower().startswith("args:"):
                in_args = True
                continue
            if in_args:
                if stripped.startswith(f"{param_name}:"):
                    return stripped.split(":", 1)[1].strip()
                if stripped and not stripped.startswith(" ") and ":" in stripped:
                    if stripped.split(":")[0].strip() != param_name:
                        continue
                if stripped.lower().startswith(("returns:", "raises:", "example:")):
                    break

        return ""

    def execute(self, **kwargs) -> Any:
        """Execute the wrapped function."""
        return self._func(**kwargs)

    def __call__(self, *args, **kwargs) -> Any:
        """Allow the tool to be called directly."""
        return self._func(*args, **kwargs)
