from textwrap import dedent
from typing import Any, Callable, Optional

from crewai.tools.base_tool import BaseTool
from pydantic import BaseModel as PydanticBaseModel


class StructuredTool(BaseTool):  # type: ignore[no-any-unimported]
    """A tool that executes functions with structured inputs using a schema."""

    func: Callable[..., Any]
    """The callable function that implements the tool's functionality."""

    def _run(self, *args: Any, **kwargs: Any) -> Any:
        """Execute the tool's function with the provided arguments."""
        return self.func(*args, **kwargs)

    @classmethod
    def from_function(
        cls,
        func: Callable,
        args_schema: type[PydanticBaseModel],
        name: Optional[str] = None,
        description: Optional[str] = None,
        **kwargs: Any,
    ) -> "StructuredTool":
        """Create a new StructuredTool instance from a function.

        Args:
            func: Function to wrap as a CrewAI tool
            name: Custom name for the tool
            description: Custom description of the tool's functionality
            args_schema: Pydantic model defining the expected argument structure
            **kwargs: Additional tool configuration parameters, like cache_function

        Returns:
            StructuredTool: A new tool instance wrapping the provided function
        """
        name = name or func.__name__

        if description is None:
            description = func.__doc__
            if description is None:
                raise ValueError("Function must have a docstring if description not provided.")
            # Clean up docstring
            description = dedent(description).strip()

        return cls(
            func=func,
            args_schema=args_schema,
            name=name,
            description=description,
            **kwargs,
        )
