import operator
from typing import Any

from graphene import ResolveInfo


class BaseOperatorPerm:
    def __init__(self, op1: Any, op2: Any):
        self.op1 = op1
        self.op2 = op2

    def __call__(self):
        return self

    def has_permission(self, info: ResolveInfo):
        return self.op_func(
            self.op1.has_permission(info), self.op2.has_permission(info)
        )

    def has_node_permission(self, info: ResolveInfo, id: str) -> bool:
        return self.op_func(
            self.op1.has_node_permission(info, id),
            self.op2.has_node_permission(info, id),
        )

    def has_mutation_permission(
        self, root: Any, info: ResolveInfo, input: dict
    ) -> bool:
        return self.op_func(
            self.op1.has_mutation_permission(root, info, input),
            self.op2.has_mutation_permission(root, info, input),
        )

    def has_filter_permission(self, info: ResolveInfo):
        return self.op_func(
            self.op1.has_filter_permission(info), self.op2.has_filter_permission(info)
        )


class BaseSingleOperatorPerm(BaseOperatorPerm):
    def __init__(self, op1: Any):
        self.op1 = op1

    def has_permission(self, info: ResolveInfo):
        return self.op_func(self.op1.has_permission(info))

    def has_node_permission(self, info: ResolveInfo, id: str) -> bool:
        return self.op_func(self.op1.has_node_permission(info, id))

    def has_mutation_permission(
        self, root: Any, info: ResolveInfo, input: dict
    ) -> bool:
        return self.op_func(self.op1.has_mutation_permission(root, info, input))

    def has_filter_permission(self, info: ResolveInfo):
        return self.op_func(self.op1.has_filter_permission(info))


class AND(BaseOperatorPerm):
    op_func = operator.and_

    def __repr__(self):
        return f"({self.op1} AND {self.op2})"


class OR(BaseOperatorPerm):
    op_func = operator.or_

    def __repr__(self):
        return f"({self.op1} OR {self.op2})"


class NOT(BaseSingleOperatorPerm):
    op_func = operator.not_

    def __repr__(self):
        return f"(NOT {self.op1})"


class BasePermissionMetaclass(type):
    def __and__(self, other):
        return AND(self, other)

    def __or__(self, other):
        return OR(self, other)

    def __rand__(self, other):
        return AND(other, self)

    def __ror__(self, other):
        return OR(other, self)

    def __invert__(self):
        return NOT(self)


class BasePermission(metaclass=BasePermissionMetaclass):
    """
    Base permission class.
    Subclass it and override methods below.
    """

    @classmethod
    def has_permission(cls, info: ResolveInfo) -> bool:
        """Fallback for other has_..._permission functions.
        Returns False by default, overwrite for custom behaviour.
        """
        return False

    @classmethod
    def has_node_permission(cls, info: ResolveInfo, id: str) -> bool:
        return cls.has_permission(info)

    @classmethod
    def has_mutation_permission(cls, root: Any, info: ResolveInfo, input: dict) -> bool:
        return cls.has_permission(info)

    @classmethod
    def has_filter_permission(cls, info: ResolveInfo) -> bool:
        return cls.has_permission(info)


class AllowAny(BasePermission):
    """
    Default authentication class.
    Allows any user for any action.
    """

    @classmethod
    def has_permission(cls, info: ResolveInfo) -> bool:
        return True


class AllowAuthenticated(BasePermission):
    """
    Allows performing action only for logged in users.
    """

    @classmethod
    def has_permission(cls, info: ResolveInfo) -> bool:
        return info.context.user.is_authenticated


class AllowStaff(BasePermission):
    """
    Allow performing action only for staff users.
    """

    @classmethod
    def has_permission(cls, info: ResolveInfo) -> bool:
        return info.context.user.is_staff


class AllowSuperuser(BasePermission):
    """
    Allow performing action only for superusers.
    """

    @classmethod
    def has_permission(cls, info: ResolveInfo) -> bool:
        return info.context.user.is_superuser
