"""Tool system for Logos ADK."""
from __future__ import annotations

import asyncio
import inspect
import time
from typing import TYPE_CHECKING, Any, Callable, get_type_hints, overload

from logos_adk.tools.registry import tool_registry

if TYPE_CHECKING:
    from logos_adk.types import ToolCall, ToolResult


class Tool:
    """A tool that can be called by an LLM."""

    def __init__(
        self,
        fn: Callable,
        name: str,
        description: str,
        *,
        schema: dict[str, Any] | None = None,
    ):
        if not asyncio.iscoroutinefunction(fn):
            raise TypeError(f"Tool function '{fn.__name__}' must be async")
        self._fn = fn
        self.name = name
        self.description = description
        self._schema: dict | None = schema

    @property
    def schema(self) -> dict:
        if self._schema is None:
            self._schema = self._build_schema()
        return self._schema

    def _build_schema(self) -> dict:
        sig = inspect.signature(self._fn)
        hints = get_type_hints(self._fn)
        properties: dict[str, Any] = {}
        required: list[str] = []

        for param_name, param in sig.parameters.items():
            if param_name == "self":
                continue
            prop: dict[str, Any] = {"type": _python_type_to_json(hints.get(param_name, str))}
            if param.default is not inspect.Parameter.empty:
                prop["default"] = param.default
            else:
                required.append(param_name)
            properties[param_name] = prop

        return {
            "name": self.name,
            "description": self.description,
            "parameters": {
                "type": "object",
                "properties": properties,
                "required": required,
            },
        }

    async def __call__(tool_self, **kwargs: Any) -> Any:
        return await tool_self._fn(**kwargs)

    async def execute(self, call: ToolCall) -> ToolResult:
        """Execute a ToolCall and return a ToolResult."""
        from logos_adk.types import ToolResult

        start = time.monotonic()
        try:
            result = await self._fn(**call.arguments)
            duration = (time.monotonic() - start) * 1000
            return ToolResult(call_id=call.id, content=result, duration_ms=duration)
        except Exception as e:
            duration = (time.monotonic() - start) * 1000
            return ToolResult(
                call_id=call.id, content=None,
                error=f"{type(e).__name__}: {e}", duration_ms=duration,
            )

    def __repr__(self) -> str:
        return f"Tool(name={self.name!r})"


def _python_type_to_json(tp: type) -> str:
    """Convert Python type to JSON Schema type string."""
    mapping = {
        str: "string",
        int: "integer",
        float: "number",
        bool: "boolean",
        list: "array",
        dict: "object",
    }
    origin = getattr(tp, "__origin__", None)
    if origin is not None:
        tp = origin
    return mapping.get(tp, "string")


@overload
def tool(fn: Callable) -> Tool: ...

@overload
def tool(*, name: str | None = None, description: str | None = None,
         schema: Any = None) -> Callable[[Callable], Tool]: ...

def tool(fn: Callable | None = None, *, name: str | None = None,
         description: str | None = None, schema: Any = None) -> Tool | Callable[[Callable], Tool]:
    """Decorator to create a Tool from an async function."""
    def decorator(f: Callable) -> Tool:
        tool_name = name or f.__name__
        tool_desc = description or (f.__doc__ or "").strip()
        explicit_schema = None
        if schema is not None:
            if not isinstance(schema, dict):
                raise TypeError("@tool(schema=...) requires a dict schema")
            explicit_schema = dict(schema)
            explicit_schema.setdefault("name", tool_name)
            explicit_schema.setdefault("description", tool_desc)
        t = Tool(fn=f, name=tool_name, description=tool_desc, schema=explicit_schema)
        tool_registry.register(tool_name, t)
        return t

    if fn is not None:
        return decorator(fn)
    return decorator
