from __future__ import annotations
import asyncio
import httpx
from dataclasses import dataclass

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.x509 import ocsp
from cryptography.x509.oid import ExtensionOID


@dataclass
class RevocationResult:
    checked: bool
    revoked: bool
    reason: str = ""
    source: str = ""
    error: str | None = None


async def check_ocsp(cert: x509.Certificate, issuer: x509.Certificate, timeout: float = 6.0) -> RevocationResult:
    try:
        aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS).value
    except x509.ExtensionNotFound:
        return RevocationResult(False, False, error="no AIA extension")
    ocsp_url = None
    for ad in aia:
        if ad.access_method.dotted_string == "1.3.6.1.5.5.7.48.1":
            try:
                ocsp_url = str(ad.access_location.value)
                break
            except Exception:
                continue
    if not ocsp_url:
        return RevocationResult(False, False, error="no OCSP URL")
    try:
        builder = ocsp.OCSPRequestBuilder()
        builder = builder.add_certificate(cert, issuer, hashes.SHA1())
        req = builder.build()
        req_bytes = req.public_bytes(serialization.Encoding.DER)
    except Exception as e:
        return RevocationResult(False, False, error=f"build error: {e}")
    headers = {
        "Content-Type": "application/ocsp-request",
        "Accept": "application/ocsp-response",
        "User-Agent": "nercone-tls-test/1.0",
    }
    try:
        async with httpx.AsyncClient(timeout=timeout) as client:
            resp = await client.post(ocsp_url, content=req_bytes, headers=headers)
    except Exception as e:
        return RevocationResult(False, False, error=f"{e.__class__.__name__}: {e}")
    if resp.status_code != 200:
        return RevocationResult(False, False, error=f"status {resp.status_code}")
    try:
        response = ocsp.load_der_ocsp_response(resp.content)
    except Exception as e:
        return RevocationResult(False, False, error=f"parse error: {e}")
    if response.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:
        return RevocationResult(False, False, error=f"ocsp status {response.response_status.name}")
    cert_status = response.certificate_status
    if cert_status == ocsp.OCSPCertStatus.REVOKED:
        return RevocationResult(True, True, reason=str(response.revocation_reason or ""), source=ocsp_url)
    if cert_status == ocsp.OCSPCertStatus.GOOD:
        return RevocationResult(True, False, source=ocsp_url)
    return RevocationResult(True, False, reason="unknown", source=ocsp_url)


async def check_crl(cert: x509.Certificate, timeout: float = 6.0) -> RevocationResult:
    try:
        cdp = cert.extensions.get_extension_for_oid(ExtensionOID.CRL_DISTRIBUTION_POINTS).value
    except x509.ExtensionNotFound:
        return RevocationResult(False, False, error="no CRL DP")
    crl_urls: list[str] = []
    for dp in cdp:
        if dp.full_name:
            for n in dp.full_name:
                try:
                    v = str(n.value)
                    if v.startswith("http://") or v.startswith("https://"):
                        crl_urls.append(v)
                except Exception:
                    continue
    if not crl_urls:
        return RevocationResult(False, False, error="no http CRL URL")
    for url in crl_urls:
        try:
            async with httpx.AsyncClient(timeout=timeout, headers={"User-Agent": "nercone-tls-test/1.0"}) as client:
                resp = await client.get(url)
            if resp.status_code != 200:
                continue
            try:
                crl = x509.load_der_x509_crl(resp.content)
            except Exception:
                try:
                    crl = x509.load_pem_x509_crl(resp.content)
                except Exception:
                    continue
            for entry in crl:
                if entry.serial_number == cert.serial_number:
                    return RevocationResult(True, True, reason="listed in CRL", source=url)
            return RevocationResult(True, False, source=url)
        except Exception:
            continue
    return RevocationResult(False, False, error="all CRLs unreachable")
