"""X.509 certificate validation for ECP Auth.

Provides validation of X.509 certificates including expiration checks,
key usage verification, chain validation, and structural validation.

Uses ``certvalidator`` for certificate chain and path validation,
and the ``cryptography`` library for individual certificate checks.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from pathlib import Path

from cryptography import x509

from ecp_auth.exceptions import (
    CertificateExpiredError,
    CertificateValidationError,
)
from ecp_auth.parsers import CertificateInfo, extract_certificate_info
from ecp_auth.settings import (
    get_allow_self_signed,
    get_require_key_usage,
    get_trusted_ca_dir,
    get_validate_chain,
)

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


def validate_certificate(certificate: x509.Certificate) -> CertificateInfo:
    """Perform full validation of an X.509 certificate.

    Runs all configured validation checks on the certificate:
    1. Certificate structure and field extraction
    2. Validity period (expiration)
    3. Key usage extensions
    4. Self-signed certificate policy
    5. Certificate chain (if enabled)

    Args:
        certificate: The X.509 certificate to validate.

    Returns:
        A ``CertificateInfo`` instance with validated certificate details.

    Raises:
        CertificateValidationError: If any validation check fails.
        CertificateExpiredError: If the certificate is expired.

    Example:
        >>> from ecp_auth.parsers import parse_pkcs12
        >>> key, cert, cas = parse_pkcs12(data, password)
        >>> info = validate_certificate(cert)
    """
    logger.info("Starting certificate validation.")

    # Step 1: Extract certificate info
    cert_info: CertificateInfo = extract_certificate_info(certificate)

    # Step 2: Validate expiration
    validate_expiration(cert_info)

    # Step 3: Validate key usage
    if get_require_key_usage():
        validate_key_usage(cert_info)

    # Step 4: Check self-signed policy
    if cert_info.is_self_signed and not get_allow_self_signed():
        raise CertificateValidationError(
            "Self-signed certificates are not allowed by the current policy."
        )

    # Step 5: Validate certificate chain
    if get_validate_chain():
        validate_chain(certificate)

    logger.info(
        "Certificate validation succeeded for subject: %s",
        cert_info.subject_dn,
    )

    return cert_info


def validate_expiration(cert_info: CertificateInfo) -> None:
    """Validate that the certificate is within its validity period.

    Args:
        cert_info: Parsed certificate information.

    Raises:
        CertificateExpiredError: If the certificate is expired or not yet valid.
    """
    now: datetime = datetime.now(timezone.utc)

    if now < cert_info.not_valid_before:
        raise CertificateExpiredError(
            f"Certificate is not yet valid. "
            f"Valid from: {cert_info.not_valid_before.isoformat()}"
        )

    if now > cert_info.not_valid_after:
        raise CertificateExpiredError(
            f"Certificate has expired. "
            f"Expired on: {cert_info.not_valid_after.isoformat()}"
        )

    logger.debug(
        "Certificate validity check passed. Valid until: %s",
        cert_info.not_valid_after.isoformat(),
    )


def validate_key_usage(cert_info: CertificateInfo) -> None:
    """Validate that the certificate has the required key usage extensions.

    Checks that the ``digitalSignature`` key usage flag is present,
    which is required for ECP authentication.

    Args:
        cert_info: Parsed certificate information.

    Raises:
        CertificateValidationError: If required key usage is missing.
    """
    if not cert_info.key_usage:
        logger.warning(
            "Certificate has no KeyUsage extension. "
            "Allowing because extension is absent."
        )
        return

    if "digital_signature" not in cert_info.key_usage:
        raise CertificateValidationError(
            "Certificate does not have the 'digitalSignature' key usage flag. "
            f"Present flags: {', '.join(cert_info.key_usage)}"
        )

    logger.debug("Key usage validation passed: digitalSignature is present.")


def validate_chain(certificate: x509.Certificate) -> None:
    """Validate the certificate chain against trusted CA certificates.

    Uses ``certvalidator`` to build and verify the certificate path
    from the end-entity certificate to a trusted root CA.

    Args:
        certificate: The end-entity X.509 certificate to validate.

    Raises:
        CertificateValidationError: If chain validation fails
            or trusted CA directory is not configured.
    """
    from cryptography.hazmat.primitives.serialization import Encoding

    trusted_ca_dir: str = get_trusted_ca_dir()

    if not trusted_ca_dir:
        raise CertificateValidationError(
            "Certificate chain validation is enabled but "
            "ECP_AUTH_TRUSTED_CA_DIR is not configured."
        )

    ca_path: Path = Path(trusted_ca_dir)
    if not ca_path.is_dir():
        raise CertificateValidationError(
            f"Trusted CA directory does not exist: {trusted_ca_dir}"
        )

    try:
        from certvalidator import CertificateValidator, ValidationContext

        # Load trusted CA certificates
        trust_roots: list[bytes] = []
        for ca_file in ca_path.glob("*.pem"):
            trust_roots.append(ca_file.read_bytes())

        for ca_file in ca_path.glob("*.crt"):
            trust_roots.append(ca_file.read_bytes())

        if not trust_roots:
            raise CertificateValidationError(
                f"No trusted CA certificates found in: {trusted_ca_dir}"
            )

        # Create validation context
        context = ValidationContext(
            trust_roots=trust_roots,
            allow_fetching=False,
        )

        # Get DER-encoded certificate
        cert_der: bytes = certificate.public_bytes(Encoding.DER)

        # Validate
        validator = CertificateValidator(cert_der, validation_context=context)
        validator.validate_usage({"digital_signature"})

        logger.info("Certificate chain validation passed.")

    except CertificateValidationError:
        raise
    except Exception as exc:
        logger.error("Certificate chain validation failed: %s", exc)
        raise CertificateValidationError(
            f"Certificate chain validation failed: {exc}"
        ) from exc


def validate_certificate_structure(certificate: x509.Certificate) -> None:
    """Validate basic certificate structure and required fields.

    Checks that the certificate contains all mandatory fields
    required for ECP authentication.

    Args:
        certificate: The X.509 certificate to validate.

    Raises:
        CertificateValidationError: If the certificate structure is invalid.
    """
    try:
        # Check that subject is not empty
        if not certificate.subject:
            raise CertificateValidationError(
                "Certificate subject is empty."
            )

        # Check serial number
        if certificate.serial_number is None:
            raise CertificateValidationError(
                "Certificate serial number is missing."
            )

        # Check public key
        if certificate.public_key() is None:
            raise CertificateValidationError(
                "Certificate does not contain a public key."
            )

        logger.debug("Certificate structure validation passed.")

    except CertificateValidationError:
        raise
    except Exception as exc:
        logger.error("Certificate structure validation failed: %s", exc)
        raise CertificateValidationError(
            f"Certificate structure validation failed: {exc}"
        ) from exc
