from __future__ import annotations
import datetime
import hashlib
from dataclasses import dataclass, field
from typing import Any

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec, dsa, ed25519, ed448


@dataclass
class CertSummary:
    subject: str
    issuer: str
    common_name: str | None
    sans: list[str] = field(default_factory=list)
    not_before: str = ""
    not_after: str = ""
    not_before_days_ago: int = 0
    days_until_expiry: int = 0
    is_expired: bool = False
    is_self_signed: bool = False
    signature_hash_algorithm: str = ""
    signature_algorithm_oid: str = ""
    public_key_algorithm: str = ""
    public_key_size_bits: int = 0
    public_key_curve: str | None = None
    serial: str = ""
    has_scts: bool = False
    sct_count: int = 0
    sha256_fingerprint: str = ""
    spki_sha256: str = ""
    aia_urls: list[str] = field(default_factory=list)
    ocsp_urls: list[str] = field(default_factory=list)
    crl_urls: list[str] = field(default_factory=list)


def _rdn(name: x509.Name) -> str:
    return ", ".join(f"{attr.oid._name}={attr.value}" for attr in name)


def _get_cn(name: x509.Name) -> str | None:
    for attr in name:
        if attr.oid == x509.NameOID.COMMON_NAME:
            return str(attr.value)
    return None


def _alg_info(pk) -> tuple[str, int, str | None]:
    if isinstance(pk, rsa.RSAPublicKey):
        return "RSA", pk.key_size, None
    if isinstance(pk, ec.EllipticCurvePublicKey):
        return "EC", pk.curve.key_size, pk.curve.name
    if isinstance(pk, dsa.DSAPublicKey):
        return "DSA", pk.key_size, None
    if isinstance(pk, ed25519.Ed25519PublicKey):
        return "Ed25519", 256, "Ed25519"
    if isinstance(pk, ed448.Ed448PublicKey):
        return "Ed448", 456, "Ed448"
    return (type(pk).__name__, 0, None)


def parse_certificate(der: bytes) -> tuple[x509.Certificate, CertSummary]:
    cert = x509.load_der_x509_certificate(der)
    s = CertSummary(
        subject=_rdn(cert.subject),
        issuer=_rdn(cert.issuer),
        common_name=_get_cn(cert.subject),
    )
    # Validity (use UTC-aware accessors when available).
    nb = getattr(cert, "not_valid_before_utc", None) or cert.not_valid_before.replace(tzinfo=datetime.timezone.utc)
    na = getattr(cert, "not_valid_after_utc", None) or cert.not_valid_after.replace(tzinfo=datetime.timezone.utc)
    now = datetime.datetime.now(tz=datetime.timezone.utc)
    s.not_before = nb.isoformat()
    s.not_after = na.isoformat()
    s.not_before_days_ago = (now - nb).days
    s.days_until_expiry = (na - now).days
    s.is_expired = na < now or nb > now

    s.is_self_signed = (cert.subject == cert.issuer)

    # Signature alg
    try:
        s.signature_hash_algorithm = cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else ""
    except Exception:
        s.signature_hash_algorithm = ""
    try:
        s.signature_algorithm_oid = cert.signature_algorithm_oid.dotted_string
    except Exception:
        s.signature_algorithm_oid = ""

    # Public key
    pk = cert.public_key()
    alg, size, curve = _alg_info(pk)
    s.public_key_algorithm = alg
    s.public_key_size_bits = size
    s.public_key_curve = curve

    # Serial
    s.serial = format(cert.serial_number, "x")

    # SANs
    try:
        san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
        names: list[str] = []
        for g in san:
            try:
                names.append(str(g.value) if hasattr(g, "value") else str(g))
            except Exception:
                continue
        s.sans = names
    except x509.ExtensionNotFound:
        pass

    # SCT (Certificate Transparency) — signed-certificate-timestamps in cert extensions.
    try:
        scts = cert.extensions.get_extension_for_class(x509.PrecertificateSignedCertificateTimestamps).value
        s.has_scts = True
        s.sct_count = len(list(scts))
    except (x509.ExtensionNotFound, Exception):
        # Could still be stapled via TLS extension — handled elsewhere.
        pass

    # AIA / OCSP / CRL
    try:
        aia = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess).value
        for ad in aia:
            try:
                if ad.access_method.dotted_string == "1.3.6.1.5.5.7.48.1":
                    s.ocsp_urls.append(str(ad.access_location.value))
                elif ad.access_method.dotted_string == "1.3.6.1.5.5.7.48.2":
                    s.aia_urls.append(str(ad.access_location.value))
            except Exception:
                continue
    except x509.ExtensionNotFound:
        pass

    try:
        cdp = cert.extensions.get_extension_for_class(x509.CRLDistributionPoints).value
        for dp in cdp:
            if dp.full_name:
                for n in dp.full_name:
                    try:
                        s.crl_urls.append(str(n.value))
                    except Exception:
                        continue
    except x509.ExtensionNotFound:
        pass

    # Fingerprints
    s.sha256_fingerprint = hashlib.sha256(der).hexdigest()
    try:
        spki = pk.public_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        )
        s.spki_sha256 = hashlib.sha256(spki).hexdigest()
    except Exception:
        s.spki_sha256 = ""

    return cert, s
