from functools import wraps

from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext as _

from wagtail.admin import messages
from wagtail.admin.localization import get_localized_response
from wagtail.log_actions import LogContext
from wagtail.permissions import page_permission_policy


def permission_denied(request):
    """Return a standard 'permission denied' response"""
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        raise PermissionDenied

    from wagtail.admin import messages

    messages.error(request, _("Sorry, you do not have permission to access this area."))
    return redirect("wagtailadmin_home")


def user_passes_test(test):
    """
    Given a test function that takes a user object and returns a boolean,
    return a view decorator that denies access to the user if the test returns false.
    """

    def decorator(view_func):
        # decorator takes the view function, and returns the view wrapped in
        # a permission check

        @wraps(view_func)
        def wrapped_view_func(request, *args, **kwargs):
            if test(request.user):
                # permission check succeeds; run the view function as normal
                return view_func(request, *args, **kwargs)
            else:
                # permission check failed
                return permission_denied(request)

        return wrapped_view_func

    return decorator


def permission_required(permission_name):
    """
    Replacement for django.contrib.auth.decorators.permission_required which returns a
    more meaningful 'permission denied' response than just redirecting to the login page.
    (The latter doesn't work anyway because Wagtail doesn't define LOGIN_URL...)
    """

    def test(user):
        return user.has_perm(permission_name)

    # user_passes_test constructs a decorator function specific to the above test function
    return user_passes_test(test)


def any_permission_required(*perms):
    """
    Decorator that accepts a list of permission names, and allows the user
    to pass if they have *any* of the permissions in the list
    """

    def test(user):
        for perm in perms:
            if user.has_perm(perm):
                return True

        return False

    return user_passes_test(test)


class PermissionPolicyChecker:
    """
    Provides a view decorator that enforces the given permission policy,
    returning the wagtailadmin 'permission denied' response if permission not granted
    """

    def __init__(self, policy):
        self.policy = policy

    def require(self, action):
        def test(user):
            return self.policy.user_has_permission(user, action)

        return user_passes_test(test)

    def require_any(self, *actions):
        def test(user):
            return self.policy.user_has_any_permission(user, actions)

        return user_passes_test(test)


def user_has_any_page_permission(user):
    """
    Check if a user has any permission to add, edit, or otherwise manage any
    page.
    """
    return page_permission_policy.user_has_any_permission(
        user, {"add", "change", "publish", "bulk_delete", "lock", "unlock"}
    )


def reject_request(request):
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        raise PermissionDenied

    # import redirect_to_login here to avoid circular imports on model files that import
    # wagtail.admin.auth, specifically where custom user models are involved
    from django.contrib.auth.views import redirect_to_login as auth_redirect_to_login

    login_url = getattr(
        settings, "WAGTAILADMIN_LOGIN_URL", reverse("wagtailadmin_login")
    )

    return auth_redirect_to_login(request.get_full_path(), login_url=login_url)


def require_admin_access(view_func):
    def decorated_view(request, *args, **kwargs):
        user = request.user

        if user.is_anonymous:
            return reject_request(request)

        if user.has_perms(["wagtailadmin.access_admin"]):
            try:
                with LogContext(user=user):
                    return get_localized_response(view_func, request, *args, **kwargs)

            except PermissionDenied:
                if request.headers.get("x-requested-with") == "XMLHttpRequest":
                    raise

                return permission_denied(request)

        if not request.headers.get("x-requested-with") == "XMLHttpRequest":
            messages.error(
                request, _("You do not have permission to access the admin.")
            )

        return reject_request(request)

    return decorated_view
