//! Certificate Revocation Checking (CRL and OCSP)
//!
//! Provides certificate revocation status checking via:
//! - OCSP (Online Certificate Status Protocol) - RFC 6960 (primary)
//! - CRL (Certificate Revocation List) - RFC 5280 (fallback)

use anyhow::{anyhow, Context, Result};
use lru::LruCache;
use once_cell::sync::Lazy;
use prometheus::{register_histogram, register_int_counter, Histogram, IntCounter};
use std::{
    num::NonZeroUsize,
    sync::Arc,
    time::{Duration, Instant},
};
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
use x509_parser::{
    certificate::X509Certificate,
    extensions::{GeneralName, ParsedExtension},
    oid_registry::asn1_rs::oid,
    prelude::FromDer,
    revocation_list::CertificateRevocationList,
    time::ASN1Time,
};

// --- Prometheus Metrics ---
// SAFETY: Prometheus metric registration fails only on duplicate names,
// which is a fatal misconfiguration. We abort rather than panic to avoid
// unwinding through FFI boundaries and to satisfy deny(clippy::unwrap_used).
static OCSP_REQUESTS_TOTAL: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!("ocsp_requests_total", "Total OCSP requests sent").unwrap_or_else(|e| {
        eprintln!("FATAL: failed to register ocsp_requests_total metric: {e}");
        std::process::abort()
    })
});
static OCSP_CACHE_HITS: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!("ocsp_cache_hits_total", "Total OCSP cache hits").unwrap_or_else(|e| {
        eprintln!("FATAL: failed to register ocsp_cache_hits_total metric: {e}");
        std::process::abort()
    })
});
static OCSP_CACHE_MISSES: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!("ocsp_cache_misses_total", "Total OCSP cache misses").unwrap_or_else(
        |e| {
            eprintln!("FATAL: failed to register ocsp_cache_misses_total metric: {e}");
            std::process::abort()
        },
    )
});
static OCSP_REVOKED_TOTAL: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!(
        "ocsp_revoked_certs_total",
        "Total certificates found to be revoked via OCSP"
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: failed to register ocsp_revoked_certs_total metric: {e}");
        std::process::abort()
    })
});
static OCSP_REQUEST_FAILURES: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!("ocsp_request_failures_total", "Total failed OCSP requests")
        .unwrap_or_else(|e| {
            eprintln!("FATAL: failed to register ocsp_request_failures_total metric: {e}");
            std::process::abort()
        })
});
static OCSP_RESPONSE_VALIDATION_FAILURES: Lazy<IntCounter> = Lazy::new(|| {
    register_int_counter!(
        "ocsp_response_validation_failures_total",
        "Total OCSP response validation failures"
    )
    .unwrap_or_else(|e| {
        eprintln!(
            "FATAL: failed to register ocsp_response_validation_failures_total metric: {e}"
        );
        std::process::abort()
    })
});
static OCSP_REQUEST_LATENCY: Lazy<Histogram> = Lazy::new(|| {
    register_histogram!("ocsp_request_latency_seconds", "Latency of OCSP requests").unwrap_or_else(
        |e| {
            eprintln!("FATAL: failed to register ocsp_request_latency_seconds metric: {e}");
            std::process::abort()
        },
    )
});

// --- OCSP Cache Implementation ---

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspStatus {
    Good,
    Revoked,
    Unknown,
}

#[derive(Debug, Clone)]
struct OcspCacheEntry {
    status: OcspStatus,
    timestamp: Instant,
}

#[derive(Debug, Clone)]
struct OcspCache {
    cache: Arc<RwLock<LruCache<String, OcspCacheEntry>>>,
    ttl: Duration,
}

impl OcspCache {
    fn new(capacity: NonZeroUsize, ttl: Duration) -> Self {
        Self {
            cache: Arc::new(RwLock::new(LruCache::new(capacity))),
            ttl,
        }
    }

    async fn get(&self, key: &str) -> Option<OcspStatus> {
        let mut cache = self.cache.write().await;
        if let Some(entry) = cache.get(key) {
            if entry.timestamp.elapsed() < self.ttl {
                OCSP_CACHE_HITS.inc();
                return Some(entry.status);
            }
        }
        OCSP_CACHE_MISSES.inc();
        None
    }

    /// Store OCSP status in cache with TTL
    async fn put(&self, key: String, status: OcspStatus) {
        let mut cache = self.cache.write().await;
        let entry = OcspCacheEntry {
            status,
            timestamp: Instant::now(),
        };
        cache.put(key, entry);
    }
}

/// Configuration for the RevocationChecker
#[derive(Debug, Clone)]
pub struct RevocationConfig {
    pub crl_url: Option<String>,
    pub ocsp_responder_url: Option<String>,
    pub ocsp_cache_ttl: Duration,
    pub ocsp_cache_capacity: NonZeroUsize,
}

/// Certificate revocation checker
#[derive(Debug, Clone)]
pub struct RevocationChecker {
    config: RevocationConfig,
    ocsp_cache: OcspCache,
    http_client: reqwest::Client,
}

impl RevocationChecker {
    /// Create new revocation checker with configuration
    pub fn new(config: RevocationConfig) -> Result<Self> {
        let http_client = reqwest::Client::builder()
            .timeout(Duration::from_secs(10))
            .build()
            .context("Failed to create HTTP client for revocation checking")?;

        let ocsp_cache = OcspCache::new(config.ocsp_cache_capacity, config.ocsp_cache_ttl);

        Ok(Self {
            config,
            ocsp_cache,
            http_client,
        })
    }

    /// Check certificate revocation status, prioritizing OCSP and falling back to CRL.
    /// A valid issuer certificate is required for OCSP.
    pub async fn check_revocation(
        &self,
        cert: &X509Certificate<'_>,
        issuer: &X509Certificate<'_>,
    ) -> Result<()> {
        // --- OCSP Check (Primary) ---
        let ocsp_urls = self.extract_ocsp_urls(cert);

        if !ocsp_urls.is_empty() {
            for ocsp_url in &ocsp_urls {
                match self.check_ocsp_revocation(cert, issuer, ocsp_url).await {
                    Ok(is_revoked) => {
                        if is_revoked {
                            OCSP_REVOKED_TOTAL.inc();
                            return Err(anyhow!(
                                "Certificate has been revoked (OCSP check against: {})",
                                ocsp_url
                            ));
                        }
                        info!("Certificate OCSP check passed: {}", ocsp_url);
                        return Ok(()); // Success, not revoked
                    },
                    Err(e) => {
                        OCSP_REQUEST_FAILURES.inc();
                        warn!("OCSP check failed for {}: {}", ocsp_url, e);
                        // Continue to next OCSP URL
                    },
                }
            }
        }

        // --- CRL Check (Fallback) ---
        if let Some(crl_url) = &self.config.crl_url {
            if !ocsp_urls.is_empty() {
                info!("OCSP checks inconclusive, falling back to CRL check.");
            }
            match self.check_crl_revocation(cert, crl_url).await {
                Ok(is_revoked) => {
                    if is_revoked {
                        return Err(anyhow!(
                            "Certificate has been revoked (CRL check against: {})",
                            crl_url
                        ));
                    }
                    info!("Certificate CRL check passed: {}", crl_url);
                    return Ok(());
                },
                Err(e) => {
                    warn!("CRL check failed for {}: {}", crl_url, e);
                    // If both OCSP and CRL fail, the entire check fails.
                    return Err(e).context("All revocation checks (OCSP and CRL) failed");
                },
            }
        }

        if ocsp_urls.is_empty() && self.config.crl_url.is_none() {
            warn!("Revocation checking is enabled, but no OCSP or CRL URLs are available for certificate with SN: {:X}", cert.serial);
            // In strict mode, this would be an error. For now, we allow it with a warning.
            return Ok(());
        }

        // If we attempted OCSP and it failed, and there's no CRL to fall back to, fail closed.
        if !ocsp_urls.is_empty() {
            return Err(anyhow!(
                "All OCSP checks failed/incomplete and no CRL fallback is configured."
            ));
        }

        Ok(())
    }

    /// Extracts OCSP responder URLs from the certificate's AIA extension,
    /// with a fallback to the configured URL.
    fn extract_ocsp_urls(&self, cert: &X509Certificate<'_>) -> Vec<String> {
        let mut urls = Vec::new();

        // OID for Authority Information Access (1.3.6.1.5.5.7.1.1)
        let aia_oid = oid!(1.3.6 .1 .5 .5 .7 .1 .1);
        // OID for OCSP access method (1.3.6.1.5.5.7.48.1)
        let ocsp_oid = oid!(1.3.6 .1 .5 .5 .7 .48 .1);

        // Extract OCSP URLs from Authority Information Access extension
        for ext in cert.extensions() {
            if ext.oid == aia_oid {
                if let ParsedExtension::AuthorityInfoAccess(aia) = ext.parsed_extension() {
                    for desc in &aia.accessdescs {
                        if desc.access_method == ocsp_oid {
                            if let GeneralName::URI(uri) = &desc.access_location {
                                urls.push(uri.to_string());
                            }
                        }
                    }
                }
            }
        }

        if let Some(fallback_url) = &self.config.ocsp_responder_url {
            if !urls.contains(fallback_url) {
                urls.push(fallback_url.clone());
            }
        }
        urls
    }

    /// Check certificate against CRL (Certificate Revocation List)
    async fn check_crl_revocation(
        &self,
        cert: &X509Certificate<'_>,
        crl_url: &str,
    ) -> Result<bool> {
        debug!("Checking certificate revocation via CRL: {}", crl_url);

        let crl_response = self
            .http_client
            .get(crl_url)
            .send()
            .await
            .context("Failed to download CRL")?;

        if !crl_response.status().is_success() {
            return Err(anyhow!(
                "Failed to download CRL: received status {}",
                crl_response.status()
            ));
        }

        let crl_bytes = crl_response
            .bytes()
            .await
            .context("Failed to read CRL response")?;

        let (_, crl) = CertificateRevocationList::from_der(&crl_bytes)
            .map_err(|e| anyhow!("Failed to parse CRL: {}", e))?;

        // Validate CRL validity period
        let now = ASN1Time::now();
        let last_update = crl.last_update();
        if now < last_update {
            warn!(
                "CRL not yet valid: thisUpdate ({}) is in the future",
                last_update
            );
            return Err(anyhow!(
                "CRL not yet valid: thisUpdate ({}) is in the future",
                last_update
            ));
        }

        if let Some(next_update) = crl.next_update() {
            if now > next_update {
                warn!(
                    "CRL has expired: nextUpdate ({}) is in the past",
                    next_update
                );
                return Err(anyhow!(
                    "CRL has expired: nextUpdate ({}) is in the past",
                    next_update
                ));
            }
            debug!(
                "CRL validity period OK: thisUpdate={}, nextUpdate={}",
                last_update, next_update
            );
        } else {
            warn!("CRL does not contain a nextUpdate field; assuming still valid");
        }

        // Validate CRL issuer matches the certificate issuer
        let crl_issuer = crl.issuer();
        let cert_issuer = cert.issuer();
        if crl_issuer != cert_issuer {
            warn!(
                "CRL issuer mismatch: CRL issuer='{}', certificate issuer='{}'",
                crl_issuer, cert_issuer
            );
            return Err(anyhow!(
                "CRL issuer '{}' does not match certificate issuer '{}'",
                crl_issuer,
                cert_issuer
            ));
        }
        debug!("CRL issuer validation passed: {}", crl_issuer);

        // Note: CRL cryptographic signature verification requires the x509-parser
        // "verify" feature (backed by ring). If enabled, call crl.verify_signature()
        // with the issuer's public key. Currently not enabled in this build.

        for revoked_cert in crl.iter_revoked_certificates() {
            if revoked_cert.raw_serial() == cert.raw_serial() {
                error!(
                    "Certificate REVOKED! Serial: {:X}, Revocation date: {:?}",
                    cert.serial, revoked_cert.revocation_date
                );
                return Ok(true);
            }
        }

        Ok(false)
    }

    /// Check certificate via OCSP (Online Certificate Status Protocol) - RFC 6960
    ///
    /// Builds an OCSP request using SHA-1 hashes of the issuer name and public key
    /// (per RFC 6960 Section 4.1.1), sends it via HTTP POST to the responder, and
    /// parses the DER-encoded response to determine revocation status.
    async fn check_ocsp_revocation(
        &self,
        cert: &X509Certificate<'_>,
        issuer: &X509Certificate<'_>,
        ocsp_url: &str,
    ) -> Result<bool> {
        debug!(
            "OCSP check requested for certificate SN {:X} against {}",
            cert.serial, ocsp_url
        );
        OCSP_REQUESTS_TOTAL.inc();
        let _timer = OCSP_REQUEST_LATENCY.start_timer();

        let cache_key = format!("{:X}", cert.serial);

        // Check cache first
        if let Some(status) = self.ocsp_cache.get(&cache_key).await {
            debug!("OCSP cache hit for SN: {}", cache_key);
            return match status {
                OcspStatus::Good => Ok(false),
                OcspStatus::Revoked => Ok(true),
                OcspStatus::Unknown => Err(anyhow!("Cached OCSP status is Unknown")),
            };
        }
        debug!("OCSP cache miss for SN: {}", cache_key);

        // Build the OCSP request DER bytes
        let ocsp_request_der = Self::build_ocsp_request(cert, issuer)
            .context("Failed to build OCSP request")?;

        // Send OCSP request via HTTP POST (RFC 6960 Appendix A)
        let response = self
            .http_client
            .post(ocsp_url)
            .header("Content-Type", "application/ocsp-request")
            .header("Accept", "application/ocsp-response")
            .body(ocsp_request_der)
            .send()
            .await
            .context("Failed to send OCSP request")?;

        if !response.status().is_success() {
            return Err(anyhow!(
                "OCSP responder returned HTTP status {}",
                response.status()
            ));
        }

        let response_bytes = response
            .bytes()
            .await
            .context("Failed to read OCSP response body")?;

        if response_bytes.is_empty() {
            return Err(anyhow!("OCSP responder returned empty response"));
        }

        // Parse the OCSP response status and certificate status
        let status = Self::parse_ocsp_response(&response_bytes, cert)
            .context("Failed to parse OCSP response")?;

        // Cache the result
        self.ocsp_cache.put(cache_key.clone(), status).await;
        debug!("OCSP result cached for SN {}: {:?}", cache_key, status);

        match status {
            OcspStatus::Good => Ok(false),
            OcspStatus::Revoked => Ok(true),
            OcspStatus::Unknown => Err(anyhow!(
                "OCSP responder returned Unknown status for certificate SN {:X}",
                cert.serial
            )),
        }
    }

    /// Encode ASN.1 DER length bytes.
    fn der_encode_length(len: usize) -> Vec<u8> {
        if len < 128 {
            vec![len as u8]
        } else {
            let be_bytes: Vec<u8> = len.to_be_bytes().into_iter().skip_while(|b| *b == 0).collect();
            let mut result = vec![0x80 | be_bytes.len() as u8];
            result.extend(be_bytes);
            result
        }
    }

    /// Wrap content bytes in an ASN.1 SEQUENCE (tag 0x30).
    fn wrap_in_sequence(content: &[u8]) -> Vec<u8> {
        let mut result = vec![0x30u8];
        result.extend(Self::der_encode_length(content.len()));
        result.extend_from_slice(content);
        result
    }

    /// Build a DER-encoded OCSP request for the given certificate and issuer.
    ///
    /// Per RFC 6960 Section 4.1.1, the CertID is constructed from:
    /// - SHA-1 hash of the issuer's distinguished name (DER encoding)
    /// - SHA-1 hash of the issuer's public key (bit string value)
    /// - The certificate's serial number
    fn build_ocsp_request(
        cert: &X509Certificate<'_>,
        issuer: &X509Certificate<'_>,
    ) -> Result<Vec<u8>> {
        use ocsp::common::asn1::{CertId, Oid};
        use ocsp::oid::ALGO_SHA1_DOT;
        use ocsp::request::OneReq;
        use sha1::{Digest, Sha1};

        // SHA-1 hash of the issuer's distinguished name (DER-encoded)
        let issuer_name_der = issuer.subject().as_raw();
        let issuer_name_hash: Vec<u8> = Sha1::digest(issuer_name_der).to_vec();

        // SHA-1 hash of the issuer's public key (BIT STRING value, excluding tag/length)
        let issuer_pubkey_data = &issuer.public_key().subject_public_key.data;
        let issuer_key_hash: Vec<u8> = Sha1::digest(issuer_pubkey_data).to_vec();

        // Certificate serial number as raw bytes
        let serial_bytes = cert.raw_serial().to_vec();

        // Build the CertId using SHA-1 algorithm OID
        let sha1_oid = Oid::new_from_dot(ALGO_SHA1_DOT)
            .map_err(|e| anyhow!("Failed to create SHA-1 OID: {:?}", e))?;
        let cert_id = CertId::new(sha1_oid, &issuer_name_hash, &issuer_key_hash, &serial_bytes);

        // Build the single request (no per-request extensions)
        let one_req = OneReq {
            certid: cert_id,
            one_req_ext: None,
        };

        // DER-encode the OneReq
        let one_req_der = one_req
            .to_der()
            .map_err(|e| anyhow!("Failed to DER-encode OneReq: {:?}", e))?;

        // Wrap in requestList SEQUENCE, then TBSRequest SEQUENCE, then OCSPRequest SEQUENCE
        let request_list = Self::wrap_in_sequence(&one_req_der);
        let tbs_request = Self::wrap_in_sequence(&request_list);
        let ocsp_request = Self::wrap_in_sequence(&tbs_request);

        debug!(
            "Built OCSP request ({} bytes) for certificate SN {:X}",
            ocsp_request.len(),
            cert.serial
        );
        Ok(ocsp_request)
    }

    /// Parse a DER-encoded OCSP response to extract the certificate status.
    ///
    /// The OCSP response structure (RFC 6960 Section 4.2.1):
    ///
    /// OCSPResponse ::= SEQUENCE {
    ///    responseStatus         OCSPResponseStatus,
    ///    responseBytes      [0] EXPLICIT ResponseBytes OPTIONAL }
    ///
    /// We extract the response status first, then scan for the certificate
    /// status tag within the response data:
    /// - 0x80 0x00 = Good
    /// - 0xa1 ...  = Revoked (with RevokedInfo)
    /// - 0x82 0x00 = Unknown
    fn parse_ocsp_response(response_bytes: &[u8], cert: &X509Certificate<'_>) -> Result<OcspStatus> {
        if response_bytes.len() < 5 {
            return Err(anyhow!(
                "OCSP response too short ({} bytes)",
                response_bytes.len()
            ));
        }

        let first_byte = response_bytes.first().copied().unwrap_or(0);
        if first_byte != 0x30 {
            return Err(anyhow!(
                "OCSP response does not start with SEQUENCE tag (got 0x{:02x})",
                first_byte
            ));
        }

        let resp_status = Self::find_response_status(response_bytes)?;

        match resp_status {
            0 => debug!("OCSP response status: successful"),
            1 => return Err(anyhow!("OCSP responder: malformed request")),
            2 => return Err(anyhow!("OCSP responder: internal error")),
            3 => return Err(anyhow!("OCSP responder: try later")),
            5 => return Err(anyhow!("OCSP responder: signature required")),
            6 => return Err(anyhow!("OCSP responder: unauthorized")),
            _ => return Err(anyhow!("OCSP responder: unknown status {}", resp_status)),
        }

        let serial_bytes = cert.raw_serial();
        let cert_status = Self::find_cert_status_in_response(response_bytes, serial_bytes)?;

        debug!("OCSP certificate status for SN {:X}: {:?}", cert.serial, cert_status);
        Ok(cert_status)
    }

    /// Extract the OCSPResponseStatus ENUMERATED value from the response.
    fn find_response_status(data: &[u8]) -> Result<u8> {
        let content = Self::parse_tlv_content(data)
            .context("Failed to parse OCSP response outer SEQUENCE")?;

        if content.len() < 3 {
            return Err(anyhow!("OCSP response content too short for status"));
        }
        let tag = content.first().copied().unwrap_or(0);
        if tag != 0x0a {
            return Err(anyhow!("Expected ENUMERATED tag (0x0a) but got 0x{:02x}", tag));
        }
        let len = content.get(1).copied().unwrap_or(0) as usize;
        if len != 1 || content.len() < 3 {
            return Err(anyhow!("Invalid ENUMERATED length in OCSP response status"));
        }
        content
            .get(2)
            .copied()
            .ok_or_else(|| anyhow!("Missing OCSP response status value"))
    }

    /// Parse a DER TLV (Tag-Length-Value) and return the value (content) portion.
    fn parse_tlv_content(data: &[u8]) -> Result<&[u8]> {
        if data.len() < 2 {
            return Err(anyhow!("TLV data too short"));
        }
        let len_byte = data.get(1).copied().unwrap_or(0);
        if len_byte & 0x80 == 0 {
            let content_start = 2;
            let content_len = len_byte as usize;
            if data.len() < content_start + content_len {
                return Err(anyhow!("TLV data truncated (short form)"));
            }
            Ok(data.get(content_start..content_start + content_len).unwrap_or(&[]))
        } else {
            let num_len_bytes = (len_byte & 0x7f) as usize;
            if num_len_bytes == 0 || num_len_bytes > 4 {
                return Err(anyhow!("Invalid long-form length byte count: {}", num_len_bytes));
            }
            let content_start = 2 + num_len_bytes;
            if data.len() < content_start {
                return Err(anyhow!("TLV data truncated (long form header)"));
            }
            let mut content_len: usize = 0;
            for i in 0..num_len_bytes {
                content_len = (content_len << 8)
                    | data
                        .get(2 + i)
                        .copied()
                        .ok_or_else(|| anyhow!("TLV long form length truncated"))?
                        as usize;
            }
            if data.len() < content_start + content_len {
                return Err(anyhow!("TLV data truncated (long form content)"));
            }
            Ok(data.get(content_start..content_start + content_len).unwrap_or(&[]))
        }
    }

    /// Search the OCSP response body for the certificate status associated with our serial.
    fn find_cert_status_in_response(data: &[u8], serial: &[u8]) -> Result<OcspStatus> {
        // Build the DER-encoded serial INTEGER to search for
        let mut serial_der = vec![0x02u8]; // INTEGER tag
        if serial.len() < 128 {
            serial_der.push(serial.len() as u8);
        } else {
            return Err(anyhow!("Serial number too long for OCSP response parsing"));
        }
        serial_der.extend_from_slice(serial);

        // Find the serial in the response data
        let serial_pos = Self::find_subsequence(data, &serial_der).ok_or_else(|| {
            OCSP_RESPONSE_VALIDATION_FAILURES.inc();
            anyhow!("Certificate serial number not found in OCSP response")
        })?;

        // After the serial INTEGER, scan forward for the cert status tag
        let search_start = serial_pos + serial_der.len();
        let search_window = data.get(search_start..).unwrap_or(&[]);
        let max_scan = std::cmp::min(search_window.len(), 128);

        for i in 0..max_scan {
            let tag = search_window.get(i).copied().unwrap_or(0);
            match tag {
                // Good: context-specific [0] IMPLICIT NULL = 0x80 0x00
                0x80 => {
                    if search_window.get(i + 1).copied().unwrap_or(0xFF) == 0x00 {
                        return Ok(OcspStatus::Good);
                    }
                }
                // Revoked: context-specific [1] CONSTRUCTED = 0xa1 with nonzero length
                0xa1 => {
                    if search_window.get(i + 1).copied().unwrap_or(0) > 0 {
                        return Ok(OcspStatus::Revoked);
                    }
                }
                // Unknown: context-specific [2] IMPLICIT NULL = 0x82 0x00
                0x82 => {
                    if search_window.get(i + 1).copied().unwrap_or(0xFF) == 0x00 {
                        return Ok(OcspStatus::Unknown);
                    }
                }
                _ => {}
            }
        }

        OCSP_RESPONSE_VALIDATION_FAILURES.inc();
        Err(anyhow!("Could not determine certificate status from OCSP response"))
    }

    /// Find a subsequence (needle) within a byte slice (haystack).
    fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
        if needle.is_empty() || needle.len() > haystack.len() {
            return None;
        }
        haystack.windows(needle.len()).position(|window| window == needle)
    }

    /// Get cache statistics for health monitoring
    pub fn get_cache_stats(&self) -> CacheStats {
        CacheStats {
            total_requests: OCSP_REQUESTS_TOTAL.get(),
            cache_hits: OCSP_CACHE_HITS.get(),
            cache_misses: OCSP_CACHE_MISSES.get(),
            revoked_certs: OCSP_REVOKED_TOTAL.get(),
            request_failures: OCSP_REQUEST_FAILURES.get(),
            validation_failures: OCSP_RESPONSE_VALIDATION_FAILURES.get(),
        }
    }
}

/// Cache statistics for monitoring
#[derive(Debug, Clone)]
pub struct CacheStats {
    pub total_requests: u64,
    pub cache_hits: u64,
    pub cache_misses: u64,
    pub revoked_certs: u64,
    pub request_failures: u64,
    pub validation_failures: u64,
}

impl CacheStats {
    /// Calculate cache hit rate (0.0 to 1.0)
    pub fn hit_rate(&self) -> f64 {
        let total_lookups = self.cache_hits + self.cache_misses;
        if total_lookups == 0 {
            0.0
        } else {
            self.cache_hits as f64 / total_lookups as f64
        }
    }

    /// Calculate failure rate (0.0 to 1.0)
    pub fn failure_rate(&self) -> f64 {
        if self.total_requests == 0 {
            0.0
        } else {
            (self.request_failures + self.validation_failures) as f64 / self.total_requests as f64
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::num::NonZeroUsize;

    #[test]
    fn test_revocation_checker_creation() {
        let config = RevocationConfig {
            crl_url: Some("http://crl.example.com/ca.crl".to_string()),
            ocsp_responder_url: Some("http://ocsp.example.com".to_string()),
            ocsp_cache_ttl: Duration::from_secs(1800),
            ocsp_cache_capacity: NonZeroUsize::new(1000).unwrap(),
        };
        let checker = RevocationChecker::new(config).unwrap();
        assert!(checker.config.crl_url.is_some());
        assert!(checker.config.ocsp_responder_url.is_some());
    }

    #[test]
    fn test_cache_stats() {
        let stats = CacheStats {
            total_requests: 100,
            cache_hits: 75,
            cache_misses: 25,
            revoked_certs: 2,
            request_failures: 5,
            validation_failures: 1,
        };

        assert_eq!(stats.hit_rate(), 0.75);
        assert_eq!(stats.failure_rate(), 0.06);
    }

    #[test]
    fn test_cache_stats_zero_requests() {
        let stats = CacheStats {
            total_requests: 0,
            cache_hits: 0,
            cache_misses: 0,
            revoked_certs: 0,
            request_failures: 0,
            validation_failures: 0,
        };

        assert_eq!(stats.hit_rate(), 0.0);
        assert_eq!(stats.failure_rate(), 0.0);
    }
}
