use crate::data_model::common::BigUnsigned;
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
use crate::data_model::rc::{
    ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
    ResourceCertificateProfileError, ResourceCertificateRole,
};
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
use ring::signature;
use x509_parser::prelude::{FromDer, X509Certificate};

use crate::validation::x509_name::x509_names_equivalent;
use std::collections::HashSet;
use x509_parser::x509::SubjectPublicKeyInfo;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidatedEeCertPath {
    pub ee: ResourceCertificate,
    pub issuer_ca: ResourceCertificate,
    pub issuer_crl: RpkixCrl,
}

#[derive(Debug, thiserror::Error)]
pub enum CertPathError {
    #[error("EE certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
    EeDecode(#[from] ResourceCertificateDecodeError),

    #[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
    IssuerDecode(ResourceCertificateDecodeError),

    #[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
    CrlDecode(#[from] CrlDecodeError),

    #[error(
        "issuer certificate must be a CA resource certificate (RFC 6487 §4.8.1; RFC 5280 §4.2.1.9)"
    )]
    IssuerNotCa,

    #[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
    IssuerProfile(ResourceCertificateProfileError),

    #[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
    EeNotEe,

    #[error(
        "EE issuer DN does not match issuer CA subject DN: ee.issuer={ee_issuer_dn} issuer.subject={issuer_subject_dn} (RFC 5280 §6.1)"
    )]
    IssuerSubjectMismatch {
        ee_issuer_dn: String,
        issuer_subject_dn: String,
    },

    #[error("EE certificate signature verification failed: {0} (RFC 5280 §6.1)")]
    EeSignatureInvalid(String),

    #[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")]
    IssuerSpkiParse(String),

    #[error(
        "trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)"
    )]
    IssuerSpkiTrailingBytes(usize),

    #[error("EE KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
    KeyUsageMissing,

    #[error("EE KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
    KeyUsageNotCritical,

    #[error("EE KeyUsage must have only digitalSignature set (RFC 6487 §4.8.4)")]
    KeyUsageInvalidBits,

    #[error("EE KeyUsage parse failed: {0} (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
    KeyUsageParse(String),

    #[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")]
    IssuerSkiMissing,

    #[error("EE authorityKeyIdentifier missing (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)")]
    EeAkiMissing,

    #[error(
        "EE authorityKeyIdentifier does not match issuer subjectKeyIdentifier (RFC 6487 §4.8.3)"
    )]
    EeAkiMismatch,

    #[error("EE authorityInfoAccess missing (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)")]
    EeAiaMissing,

    #[error(
        "EE authorityInfoAccess does not reference issuer certificate rsync URI (RFC 6487 §4.8.7)"
    )]
    EeAiaIssuerUriMismatch,

    #[error("EE CRLDistributionPoints missing (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)")]
    EeCrlDpMissing,

    #[error("EE CRLDistributionPoints does not reference issuer CRL rsync URI (RFC 6487 §4.8.6)")]
    EeCrlDpUriMismatch,

    #[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")]
    CertificateNotValidAtTime,

    #[error(
        "CRL signature/binding verification failed: {0} (RFC 5280 §6.3.3; RFC 6487 §5; RFC 9829 §3.1)"
    )]
    CrlVerify(#[from] CrlVerifyError),

    #[error(
        "CRL not valid at validation_time (RFC 5280 §6.3.3(g); RFC 5280 §5.1.2.4-§5.1.2.5; RFC 6487 §5)"
    )]
    CrlNotValidAtTime,

    #[error("EE certificate is revoked by issuer CRL (RFC 5280 §6.3.3; RFC 6487 §5)")]
    EeRevoked,
}

pub fn validate_ee_cert_path(
    ee_cert_der: &[u8],
    issuer_ca_der: &[u8],
    issuer_crl_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<ValidatedEeCertPath, CertPathError> {
    let ee = ResourceCertificate::decode_der(ee_cert_der)?;
    if ee.kind != ResourceCertKind::Ee {
        return Err(CertPathError::EeNotEe);
    }

    let issuer_ca =
        ResourceCertificate::decode_der(issuer_ca_der).map_err(CertPathError::IssuerDecode)?;
    if issuer_ca.kind != ResourceCertKind::Ca {
        return Err(CertPathError::IssuerNotCa);
    }
    issuer_ca
        .validate_rfc6487_profile(ResourceCertificateRole::Ca)
        .map_err(CertPathError::IssuerProfile)?;
    let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;

    if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
        return Err(CertPathError::IssuerSubjectMismatch {
            ee_issuer_dn: ee.tbs.issuer_name.to_string(),
            issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
        });
    }

    validate_ee_aki_matches_issuer_ski(&ee, &issuer_ca)?;
    if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
        validate_ee_aia_points_to_issuer_uri(&ee, expected_issuer_uri)?;
    }
    if let Some(expected_crl_uri) = issuer_crl_rsync_uri {
        validate_ee_crldp_contains_issuer_crl_uri(&ee, expected_crl_uri)?;
    }

    if !time_within_validity(
        validation_time,
        ee.tbs.validity_not_before,
        ee.tbs.validity_not_after,
    ) || !time_within_validity(
        validation_time,
        issuer_ca.tbs.validity_not_before,
        issuer_ca.tbs.validity_not_after,
    ) {
        return Err(CertPathError::CertificateNotValidAtTime);
    }

    let ee_x509 = parse_x509_cert(ee_cert_der)?;
    verify_ee_cert_signature(&ee_x509, &issuer_spki)?;
    validate_ee_key_usage(&ee_x509)?;

    let issuer_crl = RpkixCrl::decode_der(issuer_crl_der)?;
    issuer_crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?;
    if !crl_valid_at_time(&issuer_crl, validation_time) {
        return Err(CertPathError::CrlNotValidAtTime);
    }

    if is_serial_revoked_by_crl(&ee, &issuer_crl) {
        return Err(CertPathError::EeRevoked);
    }

    Ok(ValidatedEeCertPath {
        ee,
        issuer_ca,
        issuer_crl,
    })
}

/// Validate the EE certificate path using a *pre-decoded issuer CA* and a *pre-decoded and
/// pre-verified issuer CRL*.
///
/// This is a performance-oriented helper for stage2 serial runs: it avoids repeating issuer CA
/// decode and issuer CRL decode+signature verification for every signed object in a publication point.
///
/// The caller must ensure:
/// - `issuer_ca` corresponds to `issuer_ca_der`
/// - `issuer_crl` cryptographic signature/binding has already been verified against `issuer_ca_der`
pub fn validate_ee_cert_path_with_prevalidated_issuer(
    ee_cert_der: &[u8],
    issuer_ca: &ResourceCertificate,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
    issuer_crl: &RpkixCrl,
    issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<ResourceCertificate, CertPathError> {
    let ee = ResourceCertificate::decode_der(ee_cert_der)?;
    validate_ee_cert_path_components(
        &ee,
        ee_cert_der,
        issuer_ca,
        issuer_spki,
        issuer_crl,
        issuer_crl_revoked_serials,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        validation_time,
    )?;
    Ok(ee)
}

/// Validate the EE certificate path using a pre-decoded EE certificate and a pre-validated issuer.
///
/// This avoids re-decoding the embedded EE certificate when the caller already parsed it while
/// decoding a signed object (e.g. ROA / ASPA).
pub fn validate_ee_cert_path_with_predecoded_ee(
    ee: &ResourceCertificate,
    ee_cert_der: &[u8],
    issuer_ca: &ResourceCertificate,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
    issuer_crl: &RpkixCrl,
    issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<(), CertPathError> {
    validate_ee_cert_path_components(
        ee,
        ee_cert_der,
        issuer_ca,
        issuer_spki,
        issuer_crl,
        issuer_crl_revoked_serials,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        validation_time,
    )
}

/// Validate a signed-object embedded EE certificate path using signed-object cached fields.
///
/// This avoids reparsing the EE certificate as `X509Certificate` and bypasses the
/// x509-parser signature wrapper by verifying the cached TBS/signature bytes directly.
pub fn validate_signed_object_ee_cert_path_fast(
    ee: &ResourceEeCertificate,
    issuer_ca: &ResourceCertificate,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
    issuer_crl: &RpkixCrl,
    issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<(), CertPathError> {
    validate_ee_cert_path_bindings_and_status(
        &ee.resource_cert,
        issuer_ca,
        issuer_crl,
        issuer_crl_revoked_serials,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        validation_time,
    )?;
    validate_ee_key_usage_summary(&ee.key_usage_summary)?;
    verify_ee_cert_signature_fast(&ee.tbs_certificate_der, &ee.signature_bytes, issuer_spki)?;
    Ok(())
}

fn validate_ee_cert_path_components(
    ee: &ResourceCertificate,
    ee_cert_der: &[u8],
    issuer_ca: &ResourceCertificate,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
    issuer_crl: &RpkixCrl,
    issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<(), CertPathError> {
    validate_ee_cert_path_bindings_and_status(
        ee,
        issuer_ca,
        issuer_crl,
        issuer_crl_revoked_serials,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        validation_time,
    )?;

    let ee_x509 = parse_x509_cert(ee_cert_der)?;
    verify_ee_cert_signature(&ee_x509, issuer_spki)?;
    validate_ee_key_usage(&ee_x509)?;

    Ok(())
}

fn validate_ee_cert_path_bindings_and_status(
    ee: &ResourceCertificate,
    issuer_ca: &ResourceCertificate,
    issuer_crl: &RpkixCrl,
    issuer_crl_revoked_serials: &HashSet<Vec<u8>>,
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<(), CertPathError> {
    if ee.kind != ResourceCertKind::Ee {
        return Err(CertPathError::EeNotEe);
    }
    if issuer_ca.kind != ResourceCertKind::Ca {
        return Err(CertPathError::IssuerNotCa);
    }
    issuer_ca
        .validate_rfc6487_profile(ResourceCertificateRole::Ca)
        .map_err(CertPathError::IssuerProfile)?;

    if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
        return Err(CertPathError::IssuerSubjectMismatch {
            ee_issuer_dn: ee.tbs.issuer_name.to_string(),
            issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
        });
    }

    validate_ee_aki_matches_issuer_ski(ee, issuer_ca)?;
    if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
        validate_ee_aia_points_to_issuer_uri(ee, expected_issuer_uri)?;
    }
    if let Some(expected_crl_uri) = issuer_crl_rsync_uri {
        validate_ee_crldp_contains_issuer_crl_uri(ee, expected_crl_uri)?;
    }

    if !time_within_validity(
        validation_time,
        ee.tbs.validity_not_before,
        ee.tbs.validity_not_after,
    ) || !time_within_validity(
        validation_time,
        issuer_ca.tbs.validity_not_before,
        issuer_ca.tbs.validity_not_after,
    ) {
        return Err(CertPathError::CertificateNotValidAtTime);
    }

    if !crl_valid_at_time(issuer_crl, validation_time) {
        return Err(CertPathError::CrlNotValidAtTime);
    }

    let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number);
    if issuer_crl_revoked_serials.contains(&serial.bytes_be) {
        return Err(CertPathError::EeRevoked);
    }

    Ok(())
}

fn parse_subject_pki_from_der(der: &[u8]) -> Result<SubjectPublicKeyInfo<'_>, CertPathError> {
    let (rem, spki) = SubjectPublicKeyInfo::from_der(der)
        .map_err(|e| CertPathError::IssuerSpkiParse(e.to_string()))?;
    if !rem.is_empty() {
        return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len()));
    }
    Ok(spki)
}

fn parse_x509_cert(der: &[u8]) -> Result<X509Certificate<'_>, CertPathError> {
    let (rem, cert) = X509Certificate::from_der(der)
        .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))?;
    if !rem.is_empty() {
        return Err(CertPathError::EeSignatureInvalid(
            "trailing bytes after EE certificate".to_string(),
        ));
    }
    Ok(cert)
}

fn verify_ee_cert_signature(
    ee: &X509Certificate<'_>,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
) -> Result<(), CertPathError> {
    crate::crypto_sig_cache::verify_with_cache(
        crate::crypto_sig_cache::CryptoSigVerifyPoint::EeCertX509,
        ee.tbs_certificate.as_ref(),
        ee.signature_value.data.as_ref(),
        issuer_spki.raw,
        || {
            ee.verify_signature(Some(issuer_spki))
                .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))
        },
    )
}

fn verify_ee_cert_signature_fast(
    ee_tbs_der: &[u8],
    ee_signature_bytes: &[u8],
    issuer_spki: &SubjectPublicKeyInfo<'_>,
) -> Result<(), CertPathError> {
    crate::crypto_sig_cache::verify_with_cache(
        crate::crypto_sig_cache::CryptoSigVerifyPoint::EeCertFast,
        ee_tbs_der,
        ee_signature_bytes,
        issuer_spki.raw,
        || {
            let key = signature::UnparsedPublicKey::new(
                &signature::RSA_PKCS1_2048_8192_SHA256,
                &issuer_spki.subject_public_key.data,
            );
            key.verify(ee_tbs_der, ee_signature_bytes)
                .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))
        },
    )
}

fn validate_ee_aki_matches_issuer_ski(
    ee: &ResourceCertificate,
    issuer_ca: &ResourceCertificate,
) -> Result<(), CertPathError> {
    let Some(issuer_ski) = issuer_ca.tbs.extensions.subject_key_identifier.as_deref() else {
        return Err(CertPathError::IssuerSkiMissing);
    };
    let Some(ee_aki) = ee.tbs.extensions.authority_key_identifier.as_deref() else {
        return Err(CertPathError::EeAkiMissing);
    };
    if ee_aki != issuer_ski {
        return Err(CertPathError::EeAkiMismatch);
    }
    Ok(())
}

fn validate_ee_aia_points_to_issuer_uri(
    ee: &ResourceCertificate,
    issuer_ca_rsync_uri: &str,
) -> Result<(), CertPathError> {
    let Some(uris) = ee.tbs.extensions.ca_issuers_uris.as_ref() else {
        return Err(CertPathError::EeAiaMissing);
    };
    if uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) {
        return Ok(());
    }
    if uris.iter().any(|u| u.starts_with("rsync://")) {
        return Ok(());
    }
    Err(CertPathError::EeAiaIssuerUriMismatch)
}

fn validate_ee_crldp_contains_issuer_crl_uri(
    ee: &ResourceCertificate,
    issuer_crl_rsync_uri: &str,
) -> Result<(), CertPathError> {
    let Some(uris) = ee.tbs.extensions.crl_distribution_points_uris.as_ref() else {
        return Err(CertPathError::EeCrlDpMissing);
    };
    if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) {
        return Err(CertPathError::EeCrlDpUriMismatch);
    }
    Ok(())
}

fn validate_ee_key_usage(cert: &X509Certificate<'_>) -> Result<(), CertPathError> {
    let mut ku_critical: Option<bool> = None;
    for ext in cert.extensions() {
        if ext.oid.as_bytes() == crate::data_model::oid::OID_KEY_USAGE_RAW {
            ku_critical = Some(ext.critical);
            break;
        }
    }

    let Some(critical) = ku_critical else {
        return Err(CertPathError::KeyUsageMissing);
    };
    if !critical {
        return Err(CertPathError::KeyUsageNotCritical);
    }

    let Some(ku) = cert
        .key_usage()
        .map_err(|e| CertPathError::EeSignatureInvalid(e.to_string()))?
    else {
        return Err(CertPathError::KeyUsageMissing);
    };

    let v = &ku.value;
    let ok = v.digital_signature()
        && !v.key_cert_sign()
        && !v.crl_sign()
        && !v.non_repudiation()
        && !v.key_encipherment()
        && !v.data_encipherment()
        && !v.key_agreement()
        && !v.encipher_only()
        && !v.decipher_only();
    if !ok {
        return Err(CertPathError::KeyUsageInvalidBits);
    }
    Ok(())
}

fn validate_ee_key_usage_summary(summary: &EeKeyUsageSummary) -> Result<(), CertPathError> {
    match summary {
        EeKeyUsageSummary::DigitalSignatureOnly => Ok(()),
        EeKeyUsageSummary::Missing => Err(CertPathError::KeyUsageMissing),
        EeKeyUsageSummary::NotCritical => Err(CertPathError::KeyUsageNotCritical),
        EeKeyUsageSummary::InvalidBits => Err(CertPathError::KeyUsageInvalidBits),
        EeKeyUsageSummary::ParseError(err) => Err(CertPathError::KeyUsageParse(err.clone())),
    }
}

fn time_within_validity(
    t: time::OffsetDateTime,
    not_before: time::OffsetDateTime,
    not_after: time::OffsetDateTime,
) -> bool {
    let t = t.to_offset(time::UtcOffset::UTC);
    let not_before = not_before.to_offset(time::UtcOffset::UTC);
    let not_after = not_after.to_offset(time::UtcOffset::UTC);
    t >= not_before && t <= not_after
}

fn crl_valid_at_time(crl: &RpkixCrl, t: time::OffsetDateTime) -> bool {
    let t = t.to_offset(time::UtcOffset::UTC);
    let this_update = crl.this_update.utc.to_offset(time::UtcOffset::UTC);
    let next_update = crl.next_update.utc.to_offset(time::UtcOffset::UTC);
    t >= this_update && t < next_update
}

fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
    let serial = BigUnsigned::from_biguint(&ee.tbs.serial_number);
    crl.revoked_certs
        .iter()
        .any(|rc| rc.serial_number == serial)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::data_model::common::X509NameDer;
    use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
    use crate::data_model::rc::{
        BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
        ResourceCertificate, RpkixTbsCertificate,
    };
    use der_parser::num_bigint::BigUint;

    fn dummy_cert(
        kind: ResourceCertKind,
        subject_dn: &str,
        issuer_dn: &str,
        ski: Option<Vec<u8>>,
        aki: Option<Vec<u8>>,
        aia: Option<Vec<&str>>,
        crldp: Option<Vec<&str>>,
    ) -> ResourceCertificate {
        let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
        let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
        ResourceCertificate {
            raw_der: Vec::new(),
            kind,
            tbs: RpkixTbsCertificate {
                version: 2,
                serial_number: BigUint::from(1u8),
                signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
                issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
                subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
                validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
                validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
                subject_public_key_info: Vec::new(),
                extensions: RcExtensions {
                    basic_constraints_ca: kind == ResourceCertKind::Ca,
                    basic_constraints: (kind == ResourceCertKind::Ca).then_some(
                        BasicConstraintsProfile {
                            ca: true,
                            critical: true,
                            path_len_constraint: None,
                        },
                    ),
                    subject_key_identifier: ski,
                    authority_key_identifier: aki,
                    crl_distribution_points_uris: crldp,
                    ca_issuers_uris: aia,
                    subject_info_access: None,
                    certificate_policies_oid: (kind == ResourceCertKind::Ca)
                        .then_some(OID_CP_IPADDR_ASNUMBER.to_string()),
                    certificate_policies: (kind == ResourceCertKind::Ca).then_some(
                        CertificatePoliciesProfile {
                            policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
                            qualifier_oids: Vec::new(),
                        },
                    ),
                    extension_oids: Vec::new(),
                    ip_resources: None,
                    as_resources: None,
                },
            },
        }
    }

    #[test]
    fn ee_aki_and_aia_and_crldp_binding_checks_report_errors() {
        let issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );

        let ee = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![9]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer).unwrap_err();
        assert!(matches!(err, CertPathError::EeAkiMismatch), "{err}");

        let issuer_missing_ski = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            None,
            None,
            None,
            None,
        );
        let err = validate_ee_aki_matches_issuer_ski(&ee, &issuer_missing_ski).unwrap_err();
        assert!(matches!(err, CertPathError::IssuerSkiMissing), "{err}");

        let ee_missing_aki = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            None,
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err = validate_ee_aki_matches_issuer_ski(&ee_missing_aki, &issuer).unwrap_err();
        assert!(matches!(err, CertPathError::EeAkiMissing), "{err}");

        let ee_missing_aia = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            None,
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err = validate_ee_aia_points_to_issuer_uri(
            &ee_missing_aia,
            "rsync://example.test/issuer.cer",
        )
        .unwrap_err();
        assert!(matches!(err, CertPathError::EeAiaMissing), "{err}");

        let ee_wrong_aia = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/other.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        validate_ee_aia_points_to_issuer_uri(&ee_wrong_aia, "rsync://example.test/issuer.cer")
            .expect("non-matching rsync AIA is currently accepted");

        let ee_missing_crldp = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            None,
        );
        let err = validate_ee_crldp_contains_issuer_crl_uri(
            &ee_missing_crldp,
            "rsync://example.test/issuer.crl",
        )
        .unwrap_err();
        assert!(matches!(err, CertPathError::EeCrlDpMissing), "{err}");

        let ee_wrong_crldp = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/other.crl"]),
        );
        let err = validate_ee_crldp_contains_issuer_crl_uri(
            &ee_wrong_crldp,
            "rsync://example.test/issuer.crl",
        )
        .unwrap_err();
        assert!(matches!(err, CertPathError::EeCrlDpUriMismatch), "{err}");
    }

    #[test]
    fn ee_binding_checks_accept_when_matching() {
        let issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let ee = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        validate_ee_aki_matches_issuer_ski(&ee, &issuer).expect("aki ok");
        validate_ee_aia_points_to_issuer_uri(&ee, "rsync://example.test/issuer.cer")
            .expect("aia ok");
        validate_ee_crldp_contains_issuer_crl_uri(&ee, "rsync://example.test/issuer.crl")
            .expect("crldp ok");
    }

    #[test]
    fn signature_cache_hit_does_not_skip_crl_revocation_check() {
        use crate::crypto_sig_cache::{
            CryptoSigCache, CryptoSigVerifyPoint, compute_cache_key,
        };
        use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc};
        use crate::data_model::crl::CrlExtensions;

        let _guard = crate::crypto_sig_cache::GLOBAL_TEST_LOCK.lock().expect("test lock");
        crate::crypto_sig_cache::clear_global();

        let now = time::OffsetDateTime::now_utc();
        let mut issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        issuer.tbs.validity_not_before = now - time::Duration::days(1);
        issuer.tbs.validity_not_after = now + time::Duration::days(1);
        let mut ee_rc = dummy_cert(
            ResourceCertKind::Ee,
            "CN=ee",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        ee_rc.tbs.validity_not_before = now - time::Duration::days(1);
        ee_rc.tbs.validity_not_after = now + time::Duration::days(1);

        // Tampered "object": these bytes could never pass the real RSA verification, so an
        // Ok outcome below proves the crypto step was skipped by the cache hit.
        let ee = ResourceEeCertificate {
            raw_der: Vec::new(),
            subject_key_identifier: vec![2],
            spki_der: Vec::new(),
            rsa_public_modulus: Vec::new(),
            rsa_public_exponent: Vec::new(),
            tbs_certificate_der: b"tampered-tbs-bytes".to_vec(),
            signature_bytes: b"tampered-signature-bytes".to_vec(),
            key_usage_summary: EeKeyUsageSummary::DigitalSignatureOnly,
            sia_signed_object_uris: Vec::new(),
            resource_cert: ee_rc,
        };

        // Any real RSA SPKI works as the verifier identity (the TA fixture is a valid RSA
        // certificate); the SPKI DER only feeds the cache key here.
        let ta_der = std::fs::read(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/tests/fixtures/ta/apnic-ta.cer"
        ))
        .expect("read ta fixture");
        let (_, ta_x509) = X509Certificate::from_der(&ta_der).expect("parse ta fixture");
        let issuer_spki = &ta_x509.tbs_certificate.subject_pki;

        let crl = RpkixCrl {
            raw_der: Vec::new(),
            version: 2,
            issuer_dn: "CN=issuer".to_string(),
            signature_algorithm_oid: "1.2.840.113549.1.1.11".to_string(),
            this_update: Asn1TimeUtc {
                utc: now - time::Duration::hours(1),
                encoding: Asn1TimeEncoding::UtcTime,
            },
            next_update: Asn1TimeUtc {
                utc: now + time::Duration::hours(1),
                encoding: Asn1TimeEncoding::UtcTime,
            },
            revoked_certs: Vec::new(),
            extensions: CrlExtensions {
                authority_key_identifier: vec![1],
                crl_number: BigUnsigned { bytes_be: vec![1] },
            },
        };

        let dir = tempfile::tempdir().expect("tempdir");
        let cache = std::sync::Arc::new(CryptoSigCache::load_or_rebuild(
            dir.path().join("work-db.crypto-sig-cache"),
            true,
        ));
        cache.insert_positive(compute_cache_key(
            CryptoSigVerifyPoint::EeCertFast,
            b"tampered-tbs-bytes",
            b"tampered-signature-bytes",
            issuer_spki.raw,
        ));
        crate::crypto_sig_cache::install_global(std::sync::Arc::clone(&cache));

        // EE serial is 1 (dummy_cert); revoke it. The revocation check runs before the
        // signature step in this code path, so the result must be EeRevoked — never Ok —
        // even though a positive signature conclusion is cached.
        let revoked_serials: HashSet<Vec<u8>> = HashSet::from([vec![1u8]]);
        let err = validate_signed_object_ee_cert_path_fast(
            &ee,
            &issuer,
            issuer_spki,
            &crl,
            &revoked_serials,
            Some("rsync://example.test/issuer.cer"),
            Some("rsync://example.test/issuer.crl"),
            now,
        )
        .unwrap_err();
        assert!(
            matches!(err, CertPathError::EeRevoked),
            "cached signature conclusion must not bypass revocation: {err}"
        );

        // Not revoked: the cached positive conclusion lets validation pass even though the
        // tampered bytes could never verify — proving the crypto step was skipped.
        let not_revoked: HashSet<Vec<u8>> = HashSet::new();
        validate_signed_object_ee_cert_path_fast(
            &ee,
            &issuer,
            issuer_spki,
            &crl,
            &not_revoked,
            Some("rsync://example.test/issuer.cer"),
            Some("rsync://example.test/issuer.crl"),
            now,
        )
        .expect("cache hit skips the impossible verification");

        let summary = cache.summary();
        let stats = summary
            .per_point
            .get("ee_cert_fast")
            .expect("ee_cert_fast stats");
        assert_eq!(stats.verify_skipped, 1);
        assert_eq!(
            stats.verify_executed, 0,
            "real verification must never run on cache hits"
        );
        crate::crypto_sig_cache::clear_global();
    }
}
