"""Certificate validation and management for mTLS.

This module provides functions for loading, validating, and managing TLS certificates
for secure OTLP communication between SDK and Collector.
"""

import logging
import os
from datetime import datetime
from pathlib import Path
from typing import Optional

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

from ..utils.exceptions import ConfigurationError

logger = logging.getLogger(__name__)

EXPIRATION_WARNING_DAYS = 30


def validate_certificate_bytes(
    data: bytes, is_key: bool = False, source_path: str = "unknown"
) -> None:
    """Validate that certificate or key data is in valid PEM format.

    Args:
        data: Certificate or key content as bytes
        is_key: True if validating a private key, False for certificate
        source_path: Path of the file for error reporting

    Raises:
        ConfigurationError: If format is invalid
    """
    try:
        if is_key:
            # load_pem_private_key handles both Traditional OpenSSL and PKCS8
            serialization.load_pem_private_key(data, password=None, backend=default_backend())
        else:
            # Load as certificate
            x509.load_pem_x509_certificate(data, default_backend())

    except Exception as e:
        # SECURITY: Never log certificate/key content, only the error
        file_type = "key" if is_key else "certificate"
        raise ConfigurationError(
            f"Invalid {file_type} format at {source_path}. "
            f"Must be in PEM format. "
            f"Error: {type(e).__name__}"
        )


def check_certificate_expiration_bytes(data: bytes, source_path: str = "unknown") -> None:
    """Check certificate expiration and warn if expiring soon.

    Args:
        data: Certificate content as bytes
        source_path: Path of the file for reporting

    Raises:
        ConfigurationError: If certificate has expired

    Warns:
        If certificate expires within EXPIRATION_WARNING_DAYS days
    """
    try:
        cert = x509.load_pem_x509_certificate(data, default_backend())

        # Check if certificate has expired
        now = datetime.utcnow()
        not_after = cert.not_valid_after

        if now > not_after:
            raise ConfigurationError(
                f"Certificate has expired: {source_path}. "
                f"Expired on: {not_after.isoformat()}. "
                f"Please renew the certificate."
            )

        # Warn if certificate expires soon
        days_until_expiry = (not_after - now).days
        if days_until_expiry <= EXPIRATION_WARNING_DAYS:
            logger.warning(
                f"Certificate expires in {days_until_expiry} days ({source_path}). "
                f"Expiration date: {not_after.isoformat()}. "
                f"Consider renewing the certificate soon."
            )

    except ConfigurationError:
        # Re-raise our own errors
        raise
    except Exception as e:
        # Catch other errors (parsing, etc.)
        raise ConfigurationError(
            f"Failed to check certificate expiration at {source_path}. "
            f"Error: {type(e).__name__}: {e}"
        )


def load_certificate_file(cert_path: str) -> bytes:
    """Load certificate file and return contents.

    Args:
        cert_path: Path to certificate file

    Returns:
        Certificate file contents as bytes

    Raises:
        ConfigurationError: If file cannot be read
    """
    if not cert_path or not os.path.exists(cert_path):
        # We handle None/empty check here implicitly if validation logic sends it,
        # but type hint says str. os.path.exists(None) raises TypeError.
        # But callers should check for None before calling this if it's optional.
        # However, for required paths, we expect string.
        raise ConfigurationError(
            f"Certificate/Key file not found: {cert_path}. "
            f"Ensure the file exists and path is correct."
        )

    try:
        with open(cert_path, "rb") as f:
            return f.read()
    except Exception as e:
        raise ConfigurationError(
            f"Failed to read certificate file: {cert_path}. " f"Error: {type(e).__name__}: {e}"
        )


def validate_certificate_format(cert_path: str, is_key: bool = False) -> None:
    """Validate that certificate or key file exists and is in valid PEM format.

    Args:
        cert_path: Path to certificate or key file
        is_key: True if validating a private key file, False for certificate

    Raises:
        ConfigurationError: If certificate/key file is not found or invalid format
    """
    data = load_certificate_file(cert_path)
    validate_certificate_bytes(data, is_key, source_path=cert_path)


def check_certificate_expiration(cert_path: str) -> None:
    """Check certificate expiration and warn if expiring soon.

    Args:
        cert_path: Path to certificate file

    Raises:
        ConfigurationError: If certificate has expired
    """
    data = load_certificate_file(cert_path)
    check_certificate_expiration_bytes(data, source_path=cert_path)


def create_grpc_channel_credentials(
    mtls_enabled: bool,
    client_cert_path: Optional[str] = None,
    client_key_path: Optional[str] = None,
    ca_cert_path: Optional[str] = None,
):
    """Create gRPC channel credentials for mTLS.

    Args:
        mtls_enabled: Whether mTLS is enabled
        client_cert_path: Path to client certificate (required if mTLS enabled)
        client_key_path: Path to client private key (required if mTLS enabled)
        ca_cert_path: Path to CA certificate (required if mTLS enabled)

    Returns:
        grpc.ChannelCredentials for mTLS, or None if mTLS disabled

    Raises:
        ConfigurationError: If certificates are invalid or missing
    """
    if not mtls_enabled:
        return None

    # Load all files ONCE
    # Note: load_certificate_file checks existence
    client_cert_data = load_certificate_file(client_cert_path)
    client_key_data = load_certificate_file(client_key_path)
    ca_cert_data = load_certificate_file(ca_cert_path)

    # Validate formats using loaded data (memory efficiency)
    validate_certificate_bytes(client_cert_data, is_key=False, source_path=client_cert_path)
    validate_certificate_bytes(client_key_data, is_key=True, source_path=client_key_path)
    validate_certificate_bytes(ca_cert_data, is_key=False, source_path=ca_cert_path)

    # Check expiration
    check_certificate_expiration_bytes(client_cert_data, source_path=client_cert_path)
    check_certificate_expiration_bytes(ca_cert_data, source_path=ca_cert_path)

    # Import grpc only when needed (after validation)
    try:
        import grpc
    except ImportError:
        raise ConfigurationError(
            "grpcio is required for mTLS support. " "Install with: pip install grpcio"
        )

    # Create SSL credentials
    try:
        credentials = grpc.ssl_channel_credentials(
            root_certificates=ca_cert_data,
            private_key=client_key_data,
            certificate_chain=client_cert_data,
        )

        logger.info(
            "mTLS credentials created successfully. " f"Client cert: {Path(client_cert_path).name}"
        )

        return credentials

    except Exception as e:
        raise ConfigurationError(
            f"Failed to create gRPC SSL credentials. "
            f"Ensure certificates are valid and match. "
            f"Error: {type(e).__name__}: {e}"
        )


def validate_mtls_configuration(config) -> None:
    """Validate complete mTLS configuration.

    Validates that all required certificates are present, valid PEM format,
    and not expired.

    Args:
        config: MCAConfig instance with mTLS settings

    Raises:
        ConfigurationError: If mTLS configuration is invalid
    """
    if not config.mtls_enabled:
        return

    # Validate all certificates exist and are valid
    validate_certificate_format(config.client_cert_path, is_key=False)
    validate_certificate_format(config.client_key_path, is_key=True)
    validate_certificate_format(config.ca_cert_path, is_key=False)

    # Check expiration
    check_certificate_expiration(config.client_cert_path)
    check_certificate_expiration(config.ca_cert_path)

    logger.debug(
        "mTLS configuration validated successfully. "
        f"Certificates: client={Path(config.client_cert_path).name}, "
        f"key={Path(config.client_key_path).name}, "
        f"ca={Path(config.ca_cert_path).name}"
    )


# Alias for backward compatibility and clearer API
validate_certificate_expiry = check_certificate_expiration
