import re
from urllib.parse import urlparse

from django.conf import settings
from django.contrib.auth import backends, get_user_model, login
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.utils.deprecation import MiddlewareMixin


class TLSClientCertAuthenticationBackend(backends.ModelBackend):
    CERT_VALID_HEADER = "HTTP_X_CLIENT_CERT_VALID"
    CERT_DN_HEADER = "HTTP_X_CLIENT_CERT_DN"
    CERT_VALID_VALUE = "SUCCESS"
    CERT_CN_RE = re.compile(r"(?:^|[\s,/])CN=([^,/]+)")

    def authenticate(self, request, **kwargs):
        if request is None:
            return None

        cert_valid = request.META.get(self.CERT_VALID_HEADER)
        cert_dn = request.META.get(self.CERT_DN_HEADER)

        if cert_valid != self.CERT_VALID_VALUE or not cert_dn:
            raise RuntimeError("Invalid or missing TLS client certificate headers.")

        username = self._extract_username(cert_dn)
        if not username:
            raise RuntimeError("Unable to extract CN from X-Client-Cert-Dn header.")

        user_model = get_user_model()
        username_field = user_model.USERNAME_FIELD
        user = user_model._default_manager.filter(**{username_field: username}).first()

        if user:
            return user

        if getattr(settings, "TLS_CLIENT_AUTHENTICATION_AUTO_CREATE", False):
            return user_model._default_manager.create_user(**{username_field: username})

        raise PermissionDenied(f"User with username '{username}' does not exist.")

    def _extract_username(self, cert_dn):
        match = self.CERT_CN_RE.search(cert_dn)
        if not match:
            return None
        return match.group(1).strip()


class TLSClientCertAuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        login_path = urlparse(resolve_url(settings.LOGIN_URL)).path
        if request.path_info != login_path:
            return None

        backend = TLSClientCertAuthenticationBackend()
        user = backend.authenticate(request=request)
        if user is None:
            raise RuntimeError("TLS client certificate authentication failed.")

        user.backend = (
            f"{TLSClientCertAuthenticationBackend.__module__}."
            f"{TLSClientCertAuthenticationBackend.__qualname__}"
        )
        login(request, user)
        return HttpResponseRedirect(resolve_url(settings.LOGIN_REDIRECT_URL))
