"""Custom authentication backend for ECP Auth.

Implements Django's ``BaseBackend`` interface to authenticate users
using X.509 digital certificates and challenge-response verification.
"""

from __future__ import annotations

import logging
from typing import Any

from cryptography import x509

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
from django.http import HttpRequest

from ecp_auth.crypto import (
    Challenge,
    decode_signature_b64,
    verify_challenge_not_expired,
    verify_signature,
)
from ecp_auth.exceptions import (
    AuthenticationFailedError,
    CertificateValidationError,
    ECPAuthError,
    SignatureVerificationError,
)
from ecp_auth.models import UserCertificate
from ecp_auth.parsers import CertificateInfo, extract_certificate_info
from ecp_auth.validators import validate_certificate

logger: logging.Logger = logging.getLogger(__name__)

User = get_user_model()


class ECPAuthenticationBackend(BaseBackend):
    """Django authentication backend using ECP digital signatures.

    Authenticates users by verifying their X.509 certificate and
    validating a signed challenge (nonce). The certificate must be
    registered in the ``UserCertificate`` model and linked to a
    Django user account.

    Usage:
        Add to ``AUTHENTICATION_BACKENDS`` in settings::

            AUTHENTICATION_BACKENDS = [
                'ecp_auth.backends.ECPAuthenticationBackend',
                'django.contrib.auth.backends.ModelBackend',
            ]
    """

    def authenticate(
        self,
        request: HttpRequest | None,
        certificate: x509.Certificate | None = None,
        signature: str | None = None,
        challenge: Challenge | None = None,
        **kwargs: Any,
    ) -> Any | None:
        """Authenticate a user using their ECP certificate and signed challenge.

        The authentication flow:
        1. Validate the X.509 certificate (expiration, key usage, etc.)
        2. Verify the challenge has not expired
        3. Verify the signature against the challenge nonce
        4. Look up the user by certificate fingerprint

        Args:
            request: The HTTP request (may be ``None`` for non-web auth).
            certificate: The user's X.509 certificate.
            signature: Base64-encoded signature of the challenge nonce.
            challenge: The challenge that was signed.
            **kwargs: Additional keyword arguments (ignored).

        Returns:
            The authenticated ``User`` instance, or ``None`` if
            authentication fails.
        """
        if certificate is None or signature is None or challenge is None:
            return None

        try:
            # Step 1: Validate the certificate
            cert_info: CertificateInfo = validate_certificate(certificate)

            # Step 2: Verify challenge is still valid
            verify_challenge_not_expired(challenge)

            # Step 3: Decode and verify the signature
            signature_bytes: bytes = decode_signature_b64(signature)
            verify_signature(
                certificate=certificate,
                signature=signature_bytes,
                data=challenge.nonce_bytes,
            )

            # Step 4: Look up the user by certificate fingerprint
            user = self._get_user_by_certificate(cert_info.fingerprint_sha256)
            if user is None:
                logger.warning(
                    "No user found for certificate fingerprint: %s",
                    cert_info.fingerprint_sha256,
                )
                return None

            if not user.is_active:
                logger.warning(
                    "User %s is inactive.", user.username
                )
                return None

            logger.info(
                "ECP authentication successful for user: %s (cert: %s)",
                user.username,
                cert_info.common_name,
            )

            return user

        except ECPAuthError as exc:
            logger.warning("ECP authentication failed: %s", exc.message)
            return None
        except Exception as exc:
            logger.error("Unexpected error during ECP authentication: %s", exc)
            return None

    def get_user(self, user_id: int) -> Any | None:
        """Retrieve a user by their primary key.

        Args:
            user_id: The primary key of the user.

        Returns:
            The ``User`` instance, or ``None`` if not found.
        """
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

    def _get_user_by_certificate(self, fingerprint: str) -> Any | None:
        """Look up a user by their certificate's SHA-256 fingerprint.

        Args:
            fingerprint: SHA-256 fingerprint of the certificate.

        Returns:
            The ``User`` instance, or ``None`` if no matching
            active certificate is found.
        """
        try:
            user_cert: UserCertificate = UserCertificate.objects.select_related(
                "user"
            ).get(
                fingerprint_sha256=fingerprint,
                is_active=True,
            )
            return user_cert.user
        except UserCertificate.DoesNotExist:
            return None
