"""
Decorators for automation nodes and tools.

This module provides decorators that add metadata and validation
to automation nodes and tools, enabling better introspection
and documentation.
"""

import asyncio
import functools
from typing import Any, Callable, TypeVar

F = TypeVar("F", bound=Callable[..., Any])


def node(
    name: str | None = None,
    description: str | None = None,
    retries: int = 0,
    retry_delay: float = 1.0,
) -> Callable[[F], F]:
    """
    Decorator for marking a method as a LangGraph node.

    This decorator adds metadata to the function that can be used
    for documentation generation and runtime behavior configuration.
    Works with both sync and async node functions.

    Args:
        name: Optional name for the node (defaults to function name)
        description: Optional description of what the node does
        retries: Number of times to retry on failure (default: 0)
        retry_delay: Delay between retries in seconds (default: 1.0)

    Returns:
        Decorated function with node metadata

    Example:
        class MyAutomation(Automation[MyState]):
            @node(name="fetch_data", description="Fetches data from API")
            async def fetch_data(self, state: MyState) -> dict:
                # Fetch logic here
                return {
                    "output_data": {
                        "_version": 1,
                        "_blocks": [
                            {
                                "type": "json_data",
                                "label": "Fetched Data",
                                "data": result,
                                "display_hint": "raw",
                            }
                        ],
                    }
                }
    """

    def decorator(func: F) -> F:
        metadata = {
            "name": name or func.__name__,
            "description": description or func.__doc__ or "",
            "retries": retries,
            "retry_delay": retry_delay,
        }

        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                return await func(*args, **kwargs)

            async_wrapper._node_metadata = metadata  # type: ignore[attr-defined]
            return async_wrapper  # type: ignore[return-value]
        else:

            @functools.wraps(func)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                return func(*args, **kwargs)

            sync_wrapper._node_metadata = metadata  # type: ignore[attr-defined]
            return sync_wrapper  # type: ignore[return-value]

    return decorator


def tool(
    name: str | None = None,
    description: str | None = None,
    requires_credentials: list[str] | None = None,
) -> Callable[[F], F]:
    """
    Decorator for marking a method as a reusable tool.

    Tools are helper methods that can be called by nodes but are
    not themselves nodes in the graph. This decorator adds metadata
    for documentation and validation. Works with both sync and async functions.

    Args:
        name: Optional name for the tool (defaults to function name)
        description: Optional description of what the tool does
        requires_credentials: List of credential services the tool needs

    Returns:
        Decorated function with tool metadata

    Example:
        class MyAutomation(Automation[MyState]):
            @tool(name="send_email", requires_credentials=["gmail"])
            async def send_email(self, to: str, subject: str, body: str) -> bool:
                gmail = self.credentials.get_client("gmail")
                return gmail.send_email(to=to, subject=subject, body=body)
    """

    def decorator(func: F) -> F:
        metadata = {
            "name": name or func.__name__,
            "description": description or func.__doc__ or "",
            "requires_credentials": requires_credentials or [],
        }

        if asyncio.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                return await func(*args, **kwargs)

            async_wrapper._tool_metadata = metadata  # type: ignore[attr-defined]
            return async_wrapper  # type: ignore[return-value]
        else:

            @functools.wraps(func)
            def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
                return func(*args, **kwargs)

            sync_wrapper._tool_metadata = metadata  # type: ignore[attr-defined]
            return sync_wrapper  # type: ignore[return-value]

    return decorator


def get_node_metadata(func: Callable[..., Any]) -> dict[str, Any] | None:
    """
    Get node metadata from a decorated function.

    Args:
        func: The function to get metadata from

    Returns:
        Node metadata dict or None if not a node
    """
    return getattr(func, "_node_metadata", None)


def get_tool_metadata(func: Callable[..., Any]) -> dict[str, Any] | None:
    """
    Get tool metadata from a decorated function.

    Args:
        func: The function to get metadata from

    Returns:
        Tool metadata dict or None if not a tool
    """
    return getattr(func, "_tool_metadata", None)
