from typing import Callable, Dict, Optional

from strawberry.types import Info

PermissionChecker = Callable[[Info], bool]


class PermissionDenied(Exception):
    """Raised when a user lacks permission to perform an operation."""

    def __init__(self, operation: str, message: Optional[str] = None):
        self.operation = operation
        self.message = (
            message or f"Permission denied for operation: {operation}"
        )
        super().__init__(self.message)


class PermissionManager:
    """
    Manages permissions for CRUD operations.

    Each operation can have a custom permission checker function that receives
    the GraphQL Info context and returns True if allowed, False otherwise.
    """

    def __init__(
        self, permissions: Optional[Dict[str, PermissionChecker]] = None
    ):
        """
        Initialize permission manager.

        Args:
            permissions: Dict mapping operation names to checker functions
                        Operations: "create", "update", "delete", "list"
        """
        self.permissions: Dict[str, PermissionChecker] = permissions or {}

    def check(self, operation: str, info: Info) -> bool:
        """
        Check if operation is permitted.

        Args:
            operation: Name of the operation
            ("create", "update", "delete", "list")
            info: Strawberry Info context containing user, db, etc.

        Returns:
            True if permitted, False otherwise
        """
        if info is None or info.context is None:
            return False

        if not self.permissions:
            return True

        checker = self.permissions.get(operation)

        # No checker means operation is allowed by default
        if checker is None:
            return True

        return checker(info)

    def ensure_permission(self, operation: str, info: Info) -> None:
        """
        Ensure permission or raise PermissionDenied.

        Args:
            operation: Name of the operation
            info: Strawberry Info context

        Raises:
            PermissionDenied: If permission check fails
        """
        if not self.check(operation, info):
            raise PermissionDenied(operation)


def is_authenticated(info: Info) -> bool:
    """
    Returns True if the user is authenticated.
    Assumes info.context is a dict with a 'user' key.
    """
    user = info.context.get("user") if hasattr(info, "context") else None
    return user is not None and user != "anonymous"


def is_admin(info: Info) -> bool:
    """
    Returns True if the user has 'admin' in their roles.
    Assumes info.context is a dict with a 'roles' key.
    """
    roles = info.context.get("roles") if hasattr(info, "context") else []
    return roles is not None and "admin" in roles


def has_role(role: str) -> PermissionChecker:
    """
    Returns a permission checker that checks if the user has a specific role.
    Assumes info.context is a dict with a 'roles' key.
    """

    def checker(info: Info) -> bool:
        roles = info.context.get("roles") if hasattr(info, "context") else []
        return roles is not None and role in roles

    return checker
