from __future__ import annotations

import inspect
from dataclasses import dataclass
from typing import Any, Callable, Dict, Optional, Type

from pydantic import BaseModel, ValidationError

from .errors import ToolArgumentsError, ToolExecutionError
from .schema import build_tool_schema_from_callable
from .serialize import json_dumps


@dataclass(frozen=True)
class ToolDef:
    """A registered function tool definition."""

    name: str
    description: str
    parameters: Dict[str, Any]
    strict: bool
    func: Callable[..., Any]
    args_model: Type[BaseModel]

    @classmethod
    def from_callable(
        cls,
        func: Callable[..., Any],
        *,
        name: Optional[str] = None,
        description: Optional[str] = None,
        parameters: Optional[Dict[str, Any]] = None,
        strict: bool = True,
    ) -> "ToolDef":
        """Create ToolDef from a Python callable (schema + validator)."""
        tool_name = name or func.__name__

        resolved_description = description
        if resolved_description is None:
            resolved_description = (inspect.getdoc(func) or "").strip()

        if parameters is None:
            bundle = build_tool_schema_from_callable(func, strict=strict)
            args_model = bundle.model
            parameters_schema = bundle.parameters_schema
        else:
            # If the user provides a schema manually, we still build the args model
            # for validation based on signature/type hints.
            bundle = build_tool_schema_from_callable(func, strict=False)
            args_model = bundle.model
            parameters_schema = parameters

        return cls(
            name=tool_name,
            description=resolved_description,
            parameters=parameters_schema,
            strict=strict,
            func=func,
            args_model=args_model,
        )

    def validate_args(self, raw_args: Dict[str, Any]) -> Dict[str, Any]:
        """Validate tool arguments and return a clean dict."""
        try:
            model = self.args_model.model_validate(raw_args)
        except ValidationError as exc:
            raise ToolArgumentsError(str(exc)) from exc
        return model.model_dump()

    def call(self, raw_args: Dict[str, Any]) -> Any:
        """Validate and execute the underlying callable."""
        args = self.validate_args(raw_args)
        try:
            return self.func(**args)
        except Exception as exc:
            raise ToolExecutionError(self.name, str(exc), cause=exc) from exc

    def call_and_serialize(self, raw_args: Dict[str, Any]) -> str:
        """Execute tool and return JSON string output for function_call_output."""
        result = self.call(raw_args)
        return json_dumps(result, ensure_ascii=False)
