from lclazy import LazyLoader
from typing import Callable, TYPE_CHECKING

if TYPE_CHECKING:
    import pyseto
else:
    pyseto = LazyLoader('pyseto', globals(), 'pyseto')

from django.contrib.auth import get_user_model
from django.http import HttpRequest, HttpResponse

from encipher.tokens import UnverifiedToken
from encipher.typehints import TokenAuthRequest
from encipher.utils import lookup_user_record

from . import get_setting
import logging

logger = logging.getLogger(__name__)
User = get_user_model()


class PasetoAuthMiddleware:
    """
    Django middleware class for authenticating users using Paseto Authentication headers.
    Do not use if authentication.PasetoAuthentication is used in REST_FRAMEWORK's
    DEFAULT_AUTHENTICATION_CLASSES list.
    """

    def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
        self.get_response = get_response

    def __call__(self, request: TokenAuthRequest) -> HttpResponse:
        self.authorize_request(request)
        return self.get_response(request)

    def authorize_request(self, request: TokenAuthRequest) -> TokenAuthRequest:
        """
        Process a Django request and authenticate users.

        If a Paseto authentication header is detected and it is determined to be valid,
        the user is set as ``request.user`` and CSRF protection is disabled
        (``request._dont_enforce_csrf_checks = True``) on the request.

        :param request: Django Request instance
        """
        try:
            method, token = request.META["HTTP_AUTHORIZATION"].split(" ", 1)
            if method != get_setting("AUTH_METHOD"):
                return request
        except (KeyError, ValueError):
            return request

        try:
            access_token = UnverifiedToken(token).verify()
            access_token.verify()
        except (ValueError, pyseto.PysetoError) as e:
            logger.exception('Invalid token error: %s', e)
            return request

        user = lookup_user_record(access_token)
        if not user:
            return request

        request._dont_enforce_csrf_checks = True
        request.user = user
        return request
