"""mTLS certificate management."""

from __future__ import annotations

import ssl
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import TYPE_CHECKING

import structlog
from cryptography import x509
from cryptography.hazmat.primitives import serialization

from curadise_agent.errors import AuthenticationError

if TYPE_CHECKING:
    from pathlib import Path

log = structlog.get_logger(__name__)


@dataclass
class CertificateInfo:
    """Information about an X.509 certificate."""

    subject: str
    issuer: str
    serial_number: int
    not_valid_before: datetime
    not_valid_after: datetime
    is_ca: bool
    key_usage: list[str]

    @property
    def is_expired(self) -> bool:
        """Check if certificate is expired."""
        return datetime.now(UTC) >= self.not_valid_after

    @property
    def is_not_yet_valid(self) -> bool:
        """Check if certificate is not yet valid."""
        return datetime.now(UTC) < self.not_valid_before

    @property
    def is_valid(self) -> bool:
        """Check if certificate is currently valid."""
        now = datetime.now(UTC)
        return self.not_valid_before <= now < self.not_valid_after

    @property
    def days_until_expiry(self) -> int:
        """Days until certificate expires."""
        delta = self.not_valid_after - datetime.now(UTC)
        return max(0, delta.days)


class MTLSManager:
    """
    Manages mTLS certificates for agent authentication.

    Handles certificate loading, validation, and SSL context creation.
    """

    def __init__(
        self,
        cert_path: Path | None = None,
        key_path: Path | None = None,
        ca_path: Path | None = None,
    ) -> None:
        """
        Initialize mTLS manager.

        Args:
            cert_path: Path to client certificate
            key_path: Path to client private key
            ca_path: Path to CA certificate
        """
        self._cert_path = cert_path
        self._key_path = key_path
        self._ca_path = ca_path
        self._cert_info: CertificateInfo | None = None

    @property
    def is_configured(self) -> bool:
        """Check if mTLS is configured."""
        return self._cert_path is not None and self._key_path is not None

    @property
    def certificate_info(self) -> CertificateInfo | None:
        """Get loaded certificate information."""
        return self._cert_info

    def load_certificate(self) -> CertificateInfo:
        """
        Load and parse the client certificate.

        Returns:
            CertificateInfo with certificate details

        Raises:
            AuthenticationError: If certificate cannot be loaded
        """
        if self._cert_path is None:
            raise AuthenticationError("No certificate path configured")

        if not self._cert_path.exists():
            raise AuthenticationError(f"Certificate file not found: {self._cert_path}")

        try:
            cert_pem = self._cert_path.read_bytes()
            cert = x509.load_pem_x509_certificate(cert_pem)

            # Extract key usage
            key_usage: list[str] = []
            try:
                ku = cert.extensions.get_extension_for_class(x509.KeyUsage)
                if ku.value.digital_signature:
                    key_usage.append("digital_signature")
                if ku.value.key_encipherment:
                    key_usage.append("key_encipherment")
                if ku.value.key_cert_sign:
                    key_usage.append("key_cert_sign")
            except x509.ExtensionNotFound:
                pass

            # Check if CA
            is_ca = False
            try:
                bc = cert.extensions.get_extension_for_class(x509.BasicConstraints)
                is_ca = bc.value.ca
            except x509.ExtensionNotFound:
                pass

            self._cert_info = CertificateInfo(
                subject=cert.subject.rfc4514_string(),
                issuer=cert.issuer.rfc4514_string(),
                serial_number=cert.serial_number,
                not_valid_before=cert.not_valid_before_utc,
                not_valid_after=cert.not_valid_after_utc,
                is_ca=is_ca,
                key_usage=key_usage,
            )

            log.info(
                "certificate_loaded",
                subject=self._cert_info.subject,
                days_until_expiry=self._cert_info.days_until_expiry,
            )

            return self._cert_info

        except Exception as e:
            raise AuthenticationError(f"Failed to load certificate: {e}", cause=e) from e

    def validate_certificate(self) -> list[str]:
        """
        Validate the loaded certificate.

        Returns:
            List of validation warnings (empty if all OK)
        """
        warnings: list[str] = []

        if self._cert_info is None:
            self.load_certificate()

        if self._cert_info is None:
            return ["Certificate not loaded"]

        if self._cert_info.is_expired:
            warnings.append("Certificate is expired")
        elif self._cert_info.is_not_yet_valid:
            warnings.append("Certificate is not yet valid")

        if self._cert_info.days_until_expiry < 30:
            warnings.append(f"Certificate expires in {self._cert_info.days_until_expiry} days")

        if self._cert_info.is_ca:
            warnings.append("Certificate is a CA certificate (should be end-entity)")

        return warnings

    def create_ssl_context(self, verify: bool = True) -> ssl.SSLContext:
        """
        Create an SSL context with mTLS configuration.

        Args:
            verify: Whether to verify server certificate

        Returns:
            Configured SSLContext

        Raises:
            AuthenticationError: If configuration fails
        """
        try:
            context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)

            # Load CA certificate for server verification
            if self._ca_path:
                if not self._ca_path.exists():
                    raise AuthenticationError(f"CA certificate not found: {self._ca_path}")
                context.load_verify_locations(self._ca_path)
            elif not verify:
                context.check_hostname = False
                context.verify_mode = ssl.CERT_NONE

            # Load client certificate and key
            if self._cert_path and self._key_path:
                if not self._key_path.exists():
                    raise AuthenticationError(f"Private key not found: {self._key_path}")

                context.load_cert_chain(
                    certfile=self._cert_path,
                    keyfile=self._key_path,
                )

                log.info("ssl_context_created", mtls=True)
            else:
                log.info("ssl_context_created", mtls=False)

            return context

        except ssl.SSLError as e:
            raise AuthenticationError(f"SSL configuration failed: {e}", cause=e) from e

    def check_key_match(self) -> bool:
        """
        Verify that the certificate and private key match.

        Returns:
            True if they match
        """
        if not self._cert_path or not self._key_path:
            return False

        try:
            cert_pem = self._cert_path.read_bytes()
            key_pem = self._key_path.read_bytes()

            cert = x509.load_pem_x509_certificate(cert_pem)
            key = serialization.load_pem_private_key(key_pem, password=None)

            cert_public = cert.public_key().public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            )
            key_public = key.public_key().public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            )

            return cert_public == key_public

        except Exception as e:
            log.error("key_match_check_failed", error=str(e))
            return False


def create_mtls_manager(
    cert_path: Path | None,
    key_path: Path | None,
    ca_path: Path | None = None,
) -> MTLSManager | None:
    """
    Factory function to create mTLS manager if configured.

    Args:
        cert_path: Path to client certificate
        key_path: Path to client private key
        ca_path: Path to CA certificate

    Returns:
        MTLSManager if configured, None otherwise
    """
    if cert_path is None or key_path is None:
        return None

    return MTLSManager(
        cert_path=cert_path,
        key_path=key_path,
        ca_path=ca_path,
    )
