"""Strawberry resolver decorator for PyAuthz authorization.

The @requires() decorator checks authorization before a resolver executes.
If denied, raises Denied which Strawberry will propagate as a GraphQL error.
"""

from __future__ import annotations

import asyncio
import functools
from collections.abc import Callable
from typing import Any

from pyauthz import PyAuthz, Resource, Subject

# Type for optional resource resolver
ResourceResolver = Callable[..., Resource]


def requires(
    action: str,
    *,
    resource_resolver: ResourceResolver | None = None,
    resource_type: str | None = None,
) -> Callable:
    """Decorator that enforces authorization on a Strawberry resolver.

    Args:
        action: The action to authorize, e.g. "documents:read".
        resource_resolver: Optional callable that produces a Resource from
            the resolver's info and kwargs.
        resource_type: Optional resource type.

    Example:
        @strawberry.mutation
        @requires("documents:delete")
        async def delete_document(self, info, id: str) -> bool:
            ...
    """

    def decorator(fn: Callable) -> Callable:
        if asyncio.iscoroutinefunction(fn):

            @functools.wraps(fn)
            async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
                info = _extract_info(args, kwargs)
                _authorize(info, action, resource_resolver, resource_type, kwargs)
                return await fn(*args, **kwargs)

            return async_wrapper

        @functools.wraps(fn)
        def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
            info = _extract_info(args, kwargs)
            _authorize(info, action, resource_resolver, resource_type, kwargs)
            return fn(*args, **kwargs)

        return sync_wrapper

    return decorator


def _extract_info(args: tuple, kwargs: dict) -> Any:
    """Extract the Strawberry Info object from resolver arguments.

    Strawberry resolvers typically have signatures like:
    - def resolve(self, info, ...) -> resolvers on types
    - def resolve(info, ...) -> root resolvers
    """
    if "info" in kwargs:
        return kwargs["info"]
    # Check positional args — info is usually the first or second arg
    for arg in args:
        if hasattr(arg, "context"):
            return arg
    raise ValueError("Could not find Strawberry Info in resolver arguments")


def _authorize(
    info: Any,
    action: str,
    resource_resolver: ResourceResolver | None,
    resource_type: str | None,
    kwargs: dict,
) -> None:
    """Perform the authorization check."""
    context = info.context
    rp: PyAuthz = context["pyauthz"]
    subject: Subject = context["pyauthz_subject"]

    if resource_resolver is not None:
        resource = resource_resolver(info, **kwargs)
    else:
        if resource_type:
            rtype = resource_type
        elif ":" in action:
            rtype = action.split(":")[0]
        else:
            rtype = "unknown"
        resource = Resource(id="", type=rtype)

    decision = rp.authorize(subject, action, resource)
    context["pyauthz_decision"] = decision
