use crate::data_model::common::BigUnsigned;
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
use crate::data_model::oid::OID_KEY_USAGE_RAW;
use crate::data_model::rc::{
    AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
    ResourceCertificate, ResourceCertificateDecodeError, ResourceCertificateProfileError,
    ResourceCertificateRole,
};
use crate::policy::ResourceValidationMode;
use x509_parser::prelude::{FromDer, X509Certificate};

use crate::validation::x509_name::x509_names_equivalent;
use std::collections::{BTreeMap, HashMap, HashSet};
use x509_parser::x509::SubjectPublicKeyInfo;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidatedSubordinateCa {
    pub child_ca: ResourceCertificate,
    pub issuer_ca: ResourceCertificate,
    pub issuer_crl: RpkixCrl,
    pub effective_ip_resources: Option<IpResourceSet>,
    pub effective_as_resources: Option<AsResourceSet>,
    pub resource_warnings: ResourceValidationWarnings,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidatedSubordinateCaLite {
    pub child_ca: ResourceCertificate,
    pub effective_ip_resources: Option<IpResourceSet>,
    pub effective_as_resources: Option<AsResourceSet>,
    pub resource_warnings: ResourceValidationWarnings,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ResourceValidationWarnings {
    pub ip_reduced_by_vrs: bool,
    pub as_reduced_by_vrs: bool,
    pub ip_vrs_empty: bool,
    pub as_vrs_empty: bool,
}

impl ResourceValidationWarnings {
    pub fn is_empty(&self) -> bool {
        !self.ip_reduced_by_vrs
            && !self.as_reduced_by_vrs
            && !self.ip_vrs_empty
            && !self.as_vrs_empty
    }

    pub fn summary(&self) -> String {
        let mut parts = Vec::new();
        if self.ip_reduced_by_vrs {
            parts.push("ip_reduced_by_vrs");
        }
        if self.as_reduced_by_vrs {
            parts.push("as_reduced_by_vrs");
        }
        if self.ip_vrs_empty {
            parts.push("ip_vrs_empty");
        }
        if self.as_vrs_empty {
            parts.push("as_vrs_empty");
        }
        parts.join(",")
    }
}

#[derive(Clone, Debug, Default)]
pub struct IssuerEffectiveResourcesIndex {
    parent_ip_by_afi_items:
        Option<BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>>,
    parent_ip_merged_intervals: HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
    parent_asnum_intervals: Option<Vec<(u32, u32)>>,
    parent_rdi_intervals: Option<Vec<(u32, u32)>>,
}

impl IssuerEffectiveResourcesIndex {
    pub fn from_effective_resources(
        issuer_effective_ip: Option<&IpResourceSet>,
        issuer_effective_as: Option<&AsResourceSet>,
    ) -> Result<Self, CaPathError> {
        let parent_ip_by_afi_items = issuer_effective_ip
            .map(ip_resources_by_afi_items)
            .transpose()?;

        let parent_ip_merged_intervals = issuer_effective_ip
            .map(ip_resources_to_merged_intervals_by_afi)
            .unwrap_or_default();

        let parent_asnum_intervals = issuer_effective_as
            .and_then(|resources| resources.asnum.as_ref().map(as_choice_to_merged_intervals));
        let parent_rdi_intervals = issuer_effective_as
            .and_then(|resources| resources.rdi.as_ref().map(as_choice_to_merged_intervals));

        Ok(Self {
            parent_ip_by_afi_items,
            parent_ip_merged_intervals,
            parent_asnum_intervals,
            parent_rdi_intervals,
        })
    }
}

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

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

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

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

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

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

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

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

    #[error("child CA certificate signature verification failed: {0} (RFC 5280 §6.1)")]
    ChildSignatureInvalid(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("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")]
    CertificateNotValidAtTime,

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

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

    #[error("child CA KeyUsage must have only keyCertSign and cRLSign set (RFC 6487 §4.8.4)")]
    KeyUsageInvalidBits,

    #[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("child CA certificate is revoked by issuer CRL (RFC 5280 §6.3.3; RFC 6487 §5)")]
    ChildRevoked,

    #[error(
        "child CA certificate must contain at least one RFC 3779 resource extension (IP or AS) (RFC 6487 §4.8.10-§4.8.11)"
    )]
    ResourcesMissing,

    #[error(
        "resource extension inheritance cannot be resolved (parent missing resources) (RFC 6487 §7.2)"
    )]
    InheritWithoutParentResources,

    #[error("child CA resources are not a subset of issuer resources (RFC 6487 §7.2)")]
    ResourcesNotSubset,

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

    #[error("child CA authorityKeyIdentifier missing (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)")]
    ChildAkiMissing,

    #[error(
        "child CA authorityKeyIdentifier does not match issuer subjectKeyIdentifier (RFC 6487 §4.8.3)"
    )]
    ChildAkiMismatch,

    #[error("child CA authorityInfoAccess missing (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)")]
    ChildAiaMissing,

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

    #[error("child CA CRLDistributionPoints missing (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)")]
    ChildCrlDpMissing,

    #[error(
        "child CA CRLDistributionPoints does not reference issuer CRL rsync URI (RFC 6487 §4.8.6)"
    )]
    ChildCrlDpUriMismatch,
}

pub fn validate_subordinate_ca_cert(
    child_ca_der: &[u8],
    issuer_ca_der: &[u8],
    issuer_crl_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: &str,
    issuer_effective_ip: Option<&IpResourceSet>,
    issuer_effective_as: Option<&AsResourceSet>,
    validation_time: time::OffsetDateTime,
) -> Result<ValidatedSubordinateCa, CaPathError> {
    validate_subordinate_ca_cert_with_resource_validation_mode(
        child_ca_der,
        issuer_ca_der,
        issuer_crl_der,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        issuer_effective_ip,
        issuer_effective_as,
        validation_time,
        ResourceValidationMode::Rfc6487,
    )
}

pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
    child_ca_der: &[u8],
    issuer_ca_der: &[u8],
    issuer_crl_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    issuer_crl_rsync_uri: &str,
    issuer_effective_ip: Option<&IpResourceSet>,
    issuer_effective_as: Option<&AsResourceSet>,
    validation_time: time::OffsetDateTime,
    resource_validation_mode: ResourceValidationMode,
) -> Result<ValidatedSubordinateCa, CaPathError> {
    let child_ca = ResourceCertificate::decode_der(child_ca_der)?;
    if child_ca.kind != ResourceCertKind::Ca {
        return Err(CaPathError::ChildNotCa);
    }
    child_ca
        .validate_rfc6487_profile(ResourceCertificateRole::Ca)
        .map_err(CaPathError::ChildProfile)?;

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

    if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
        return Err(CaPathError::IssuerSubjectMismatch {
            child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
            issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
        });
    }

    validate_child_aki_matches_issuer_ski(&child_ca, &issuer_ca)?;
    if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
        validate_child_aia_points_to_issuer_uri(&child_ca, expected_issuer_uri)?;
    }
    validate_child_crldp_contains_issuer_crl_uri(&child_ca, issuer_crl_rsync_uri)?;

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

    let child_x509 = parse_x509_cert(child_ca_der)?;
    verify_child_signature(&child_x509, &issuer_spki)?;
    validate_child_ca_key_usage(&child_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(CaPathError::CrlNotValidAtTime);
    }

    if is_serial_revoked_by_crl(&child_ca, &issuer_crl) {
        return Err(CaPathError::ChildRevoked);
    }

    let ResourceResolution {
        effective_ip_resources,
        effective_as_resources,
        warnings: resource_warnings,
    } = resolve_child_resources(
        child_ca.tbs.extensions.ip_resources.as_ref(),
        issuer_effective_ip,
        child_ca.tbs.extensions.as_resources.as_ref(),
        issuer_effective_as,
        &IssuerEffectiveResourcesIndex::from_effective_resources(
            issuer_effective_ip,
            issuer_effective_as,
        )?,
        resource_validation_mode,
    )?;
    if effective_ip_resources.is_none() && effective_as_resources.is_none() {
        return Err(CaPathError::ResourcesMissing);
    }

    Ok(ValidatedSubordinateCa {
        child_ca,
        issuer_ca,
        issuer_crl,
        effective_ip_resources,
        effective_as_resources,
        resource_warnings,
    })
}

/// Validate a subordinate child CA using *pre-decoded issuer CA* and *pre-decoded+verified issuer CRL*.
///
/// This avoids repeating issuer CA decode and issuer CRL decode+signature verification for every
/// child CA certificate discovered in a publication point.
pub fn validate_subordinate_ca_cert_with_prevalidated_issuer(
    child_ca_der: &[u8],
    child_ca: ResourceCertificate,
    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: &str,
    issuer_effective_ip: Option<&IpResourceSet>,
    issuer_effective_as: Option<&AsResourceSet>,
    validation_time: time::OffsetDateTime,
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
    let issuer_resources_index = IssuerEffectiveResourcesIndex::from_effective_resources(
        issuer_effective_ip,
        issuer_effective_as,
    )?;
    validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
        child_ca_der,
        child_ca,
        issuer_ca,
        issuer_spki,
        issuer_crl,
        issuer_crl_revoked_serials,
        issuer_ca_rsync_uri,
        issuer_crl_rsync_uri,
        issuer_effective_ip,
        issuer_effective_as,
        &issuer_resources_index,
        validation_time,
        ResourceValidationMode::Rfc6487,
    )
}

pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
    child_ca_der: &[u8],
    child_ca: ResourceCertificate,
    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: &str,
    issuer_effective_ip: Option<&IpResourceSet>,
    issuer_effective_as: Option<&AsResourceSet>,
    issuer_resources_index: &IssuerEffectiveResourcesIndex,
    validation_time: time::OffsetDateTime,
    resource_validation_mode: ResourceValidationMode,
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
    if child_ca.kind != ResourceCertKind::Ca {
        return Err(CaPathError::ChildNotCa);
    }
    if issuer_ca.kind != ResourceCertKind::Ca {
        return Err(CaPathError::IssuerNotCa);
    }
    child_ca
        .validate_rfc6487_profile(ResourceCertificateRole::Ca)
        .map_err(CaPathError::ChildProfile)?;
    issuer_ca
        .validate_rfc6487_profile(ResourceCertificateRole::Ca)
        .map_err(CaPathError::IssuerProfile)?;

    if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
        return Err(CaPathError::IssuerSubjectMismatch {
            child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
            issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
        });
    }

    validate_child_aki_matches_issuer_ski(&child_ca, issuer_ca)?;
    if let Some(expected_issuer_uri) = issuer_ca_rsync_uri {
        validate_child_aia_points_to_issuer_uri(&child_ca, expected_issuer_uri)?;
    }
    validate_child_crldp_contains_issuer_crl_uri(&child_ca, issuer_crl_rsync_uri)?;

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

    let child_x509 = parse_x509_cert(child_ca_der)?;
    verify_child_signature(&child_x509, issuer_spki)?;
    validate_child_ca_key_usage(&child_x509)?;

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

    let serial = BigUnsigned::from_biguint(&child_ca.tbs.serial_number);
    if issuer_crl_revoked_serials.contains(&serial.bytes_be) {
        return Err(CaPathError::ChildRevoked);
    }

    let ResourceResolution {
        effective_ip_resources,
        effective_as_resources,
        warnings: resource_warnings,
    } = resolve_child_resources(
        child_ca.tbs.extensions.ip_resources.as_ref(),
        issuer_effective_ip,
        child_ca.tbs.extensions.as_resources.as_ref(),
        issuer_effective_as,
        issuer_resources_index,
        resource_validation_mode,
    )?;
    if effective_ip_resources.is_none() && effective_as_resources.is_none() {
        return Err(CaPathError::ResourcesMissing);
    }

    Ok(ValidatedSubordinateCaLite {
        child_ca,
        effective_ip_resources,
        effective_as_resources,
        resource_warnings,
    })
}

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

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

fn verify_child_signature(
    child: &X509Certificate<'_>,
    issuer_spki: &SubjectPublicKeyInfo<'_>,
) -> Result<(), CaPathError> {
    crate::crypto_sig_cache::verify_with_cache(
        crate::crypto_sig_cache::CryptoSigVerifyPoint::ChildCaCert,
        child.tbs_certificate.as_ref(),
        child.signature_value.data.as_ref(),
        issuer_spki.raw,
        || {
            child
                .verify_signature(Some(issuer_spki))
                .map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))
        },
    )
}

fn validate_child_aki_matches_issuer_ski(
    child: &ResourceCertificate,
    issuer: &ResourceCertificate,
) -> Result<(), CaPathError> {
    let Some(issuer_ski) = issuer.tbs.extensions.subject_key_identifier.as_deref() else {
        return Err(CaPathError::IssuerSkiMissing);
    };
    let Some(child_aki) = child.tbs.extensions.authority_key_identifier.as_deref() else {
        return Err(CaPathError::ChildAkiMissing);
    };
    if child_aki != issuer_ski {
        return Err(CaPathError::ChildAkiMismatch);
    }
    Ok(())
}

fn validate_child_aia_points_to_issuer_uri(
    child: &ResourceCertificate,
    issuer_ca_rsync_uri: &str,
) -> Result<(), CaPathError> {
    let Some(uris) = child.tbs.extensions.ca_issuers_uris.as_ref() else {
        return Err(CaPathError::ChildAiaMissing);
    };
    if !uris.iter().any(|u| u.as_str() == issuer_ca_rsync_uri) {
        return Err(CaPathError::ChildAiaIssuerUriMismatch);
    }
    Ok(())
}

fn validate_child_crldp_contains_issuer_crl_uri(
    child: &ResourceCertificate,
    issuer_crl_rsync_uri: &str,
) -> Result<(), CaPathError> {
    let Some(uris) = child.tbs.extensions.crl_distribution_points_uris.as_ref() else {
        return Err(CaPathError::ChildCrlDpMissing);
    };
    if !uris.iter().any(|u| u.as_str() == issuer_crl_rsync_uri) {
        return Err(CaPathError::ChildCrlDpUriMismatch);
    }
    Ok(())
}

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

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

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

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

    Ok(())
}

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(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool {
    let serial = BigUnsigned::from_biguint(&cert.tbs.serial_number);
    crl.revoked_certs
        .iter()
        .any(|rc| rc.serial_number == serial)
}

#[derive(Clone, Debug)]
struct ResourceResolution {
    effective_ip_resources: Option<IpResourceSet>,
    effective_as_resources: Option<AsResourceSet>,
    warnings: ResourceValidationWarnings,
}

fn resolve_child_resources(
    child_ip: Option<&IpResourceSet>,
    issuer_effective_ip: Option<&IpResourceSet>,
    child_as: Option<&AsResourceSet>,
    issuer_effective_as: Option<&AsResourceSet>,
    issuer_resources_index: &IssuerEffectiveResourcesIndex,
    mode: ResourceValidationMode,
) -> Result<ResourceResolution, CaPathError> {
    match mode {
        ResourceValidationMode::Rfc6487 => Ok(ResourceResolution {
            effective_ip_resources: resolve_child_ip_resources_indexed(
                child_ip,
                issuer_effective_ip,
                issuer_resources_index.parent_ip_by_afi_items.as_ref(),
                &issuer_resources_index.parent_ip_merged_intervals,
            )?,
            effective_as_resources: resolve_child_as_resources_indexed(
                child_as,
                issuer_effective_as,
                issuer_resources_index.parent_asnum_intervals.as_deref(),
                issuer_resources_index.parent_rdi_intervals.as_deref(),
            )?,
            warnings: ResourceValidationWarnings::default(),
        }),
        ResourceValidationMode::ValidationUpdate03 => {
            let (effective_ip_resources, ip_reduced_by_vrs, ip_vrs_empty) =
                resolve_child_ip_resources_vrs(
                    child_ip,
                    issuer_effective_ip,
                    issuer_resources_index.parent_ip_by_afi_items.as_ref(),
                    &issuer_resources_index.parent_ip_merged_intervals,
                )?;
            let (effective_as_resources, as_reduced_by_vrs, as_vrs_empty) =
                resolve_child_as_resources_vrs(
                    child_as,
                    issuer_effective_as,
                    issuer_resources_index.parent_asnum_intervals.as_deref(),
                    issuer_resources_index.parent_rdi_intervals.as_deref(),
                )?;
            Ok(ResourceResolution {
                effective_ip_resources,
                effective_as_resources,
                warnings: ResourceValidationWarnings {
                    ip_reduced_by_vrs,
                    as_reduced_by_vrs,
                    ip_vrs_empty,
                    as_vrs_empty,
                },
            })
        }
    }
}

fn resolve_child_ip_resources(
    child_ip: Option<&IpResourceSet>,
    issuer_effective: Option<&IpResourceSet>,
) -> Result<Option<IpResourceSet>, CaPathError> {
    let precomputed_parent_by_afi = issuer_effective
        .map(ip_resources_by_afi_items)
        .transpose()?;
    let precomputed_parent_intervals = issuer_effective
        .map(ip_resources_to_merged_intervals_by_afi)
        .unwrap_or_default();
    resolve_child_ip_resources_indexed(
        child_ip,
        issuer_effective,
        precomputed_parent_by_afi.as_ref(),
        &precomputed_parent_intervals,
    )
}

fn resolve_child_ip_resources_indexed(
    child_ip: Option<&IpResourceSet>,
    issuer_effective: Option<&IpResourceSet>,
    parent_by_afi: Option<
        &BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
    >,
    parent_intervals_by_afi: &HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
) -> Result<Option<IpResourceSet>, CaPathError> {
    let Some(child_ip) = child_ip else {
        return Ok(None);
    };

    let Some(_parent) = issuer_effective else {
        if child_ip.has_any_inherit() {
            return Err(CaPathError::InheritWithoutParentResources);
        }
        // With no parent effective resources, we cannot validate subset.
        return Err(CaPathError::ResourcesNotSubset);
    };

    // Resolve per-AFI inherit, producing an effective set with no inherit.
    let parent_by_afi = parent_by_afi.ok_or(CaPathError::InheritWithoutParentResources)?;
    let mut out_families: Vec<crate::data_model::rc::IpAddressFamily> = Vec::new();

    for fam in &child_ip.families {
        match &fam.choice {
            IpAddressChoice::Inherit => {
                let items = parent_by_afi
                    .get(&fam.afi)
                    .ok_or(CaPathError::InheritWithoutParentResources)?;
                out_families.push(crate::data_model::rc::IpAddressFamily {
                    afi: fam.afi,
                    choice: IpAddressChoice::AddressesOrRanges(items.clone()),
                });
            }
            IpAddressChoice::AddressesOrRanges(items) => {
                // Subset check against parent union for that AFI.
                let parent_intervals = parent_intervals_by_afi
                    .get(&fam.afi)
                    .map(Vec::as_slice)
                    .unwrap_or(&[]);
                if !ip_family_items_subset_with_parent_intervals(items, parent_intervals) {
                    return Err(CaPathError::ResourcesNotSubset);
                }
                out_families.push(crate::data_model::rc::IpAddressFamily {
                    afi: fam.afi,
                    choice: IpAddressChoice::AddressesOrRanges(items.clone()),
                });
            }
        }
    }

    Ok(Some(IpResourceSet {
        families: out_families,
    }))
}

fn resolve_child_ip_resources_vrs(
    child_ip: Option<&IpResourceSet>,
    issuer_effective: Option<&IpResourceSet>,
    parent_by_afi: Option<
        &BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
    >,
    parent_intervals_by_afi: &HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
) -> Result<(Option<IpResourceSet>, bool, bool), CaPathError> {
    let Some(child_ip) = child_ip else {
        return Ok((None, false, false));
    };
    if child_ip.has_any_inherit() && issuer_effective.is_none() {
        return Err(CaPathError::InheritWithoutParentResources);
    }

    let parent_by_afi = parent_by_afi.unwrap_or_else(|| {
        static EMPTY: std::sync::OnceLock<
            BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
        > = std::sync::OnceLock::new();
        EMPTY.get_or_init(BTreeMap::new)
    });
    let mut out_families = Vec::new();
    let mut reduced = false;
    let mut saw_declared = false;

    for fam in &child_ip.families {
        match &fam.choice {
            IpAddressChoice::Inherit => {
                let items = parent_by_afi
                    .get(&fam.afi)
                    .ok_or(CaPathError::InheritWithoutParentResources)?;
                out_families.push(crate::data_model::rc::IpAddressFamily {
                    afi: fam.afi,
                    choice: IpAddressChoice::AddressesOrRanges(items.clone()),
                });
            }
            IpAddressChoice::AddressesOrRanges(items) => {
                saw_declared = saw_declared || !items.is_empty();
                let parent_intervals = parent_intervals_by_afi
                    .get(&fam.afi)
                    .map(Vec::as_slice)
                    .unwrap_or(&[]);
                let intersections =
                    intersect_ip_items_with_parent_intervals(items, parent_intervals);
                let child_intervals = ip_items_to_merged_intervals(items);
                if intervals_changed(&child_intervals, &intersections) {
                    reduced = true;
                }
                if !intersections.is_empty() {
                    out_families.push(crate::data_model::rc::IpAddressFamily {
                        afi: fam.afi,
                        choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges(
                            fam.afi,
                            &intersections,
                        )),
                    });
                }
            }
        }
    }

    let empty = saw_declared && out_families.is_empty();
    Ok((
        Some(IpResourceSet {
            families: out_families,
        }),
        reduced,
        empty,
    ))
}

fn resolve_child_as_resources(
    child_as: Option<&AsResourceSet>,
    issuer_effective: Option<&AsResourceSet>,
) -> Result<Option<AsResourceSet>, CaPathError> {
    let precomputed_asnum = issuer_effective
        .and_then(|resources| resources.asnum.as_ref().map(as_choice_to_merged_intervals));
    let precomputed_rdi = issuer_effective
        .and_then(|resources| resources.rdi.as_ref().map(as_choice_to_merged_intervals));
    resolve_child_as_resources_indexed(
        child_as,
        issuer_effective,
        precomputed_asnum.as_deref(),
        precomputed_rdi.as_deref(),
    )
}

fn resolve_child_as_resources_indexed(
    child_as: Option<&AsResourceSet>,
    issuer_effective: Option<&AsResourceSet>,
    parent_asnum_intervals: Option<&[(u32, u32)]>,
    parent_rdi_intervals: Option<&[(u32, u32)]>,
) -> Result<Option<AsResourceSet>, CaPathError> {
    let Some(child_as) = child_as else {
        return Ok(None);
    };
    let Some(parent) = issuer_effective else {
        if matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit))
            || matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit))
        {
            return Err(CaPathError::InheritWithoutParentResources);
        }
        return Err(CaPathError::ResourcesNotSubset);
    };

    let asnum = match child_as.asnum.as_ref() {
        None => None,
        Some(AsIdentifierChoice::Inherit) => parent
            .asnum
            .clone()
            .ok_or(CaPathError::InheritWithoutParentResources)
            .map(Some)?,
        Some(_) => {
            if !as_choice_subset_with_parent_intervals(
                child_as.asnum.as_ref(),
                parent.asnum.as_ref(),
                parent_asnum_intervals,
            ) {
                return Err(CaPathError::ResourcesNotSubset);
            }
            child_as.asnum.clone()
        }
    };

    let rdi = match child_as.rdi.as_ref() {
        None => None,
        Some(AsIdentifierChoice::Inherit) => parent
            .rdi
            .clone()
            .ok_or(CaPathError::InheritWithoutParentResources)
            .map(Some)?,
        Some(_) => {
            if !as_choice_subset_with_parent_intervals(
                child_as.rdi.as_ref(),
                parent.rdi.as_ref(),
                parent_rdi_intervals,
            ) {
                return Err(CaPathError::ResourcesNotSubset);
            }
            child_as.rdi.clone()
        }
    };

    Ok(Some(AsResourceSet { asnum, rdi }))
}

fn resolve_child_as_resources_vrs(
    child_as: Option<&AsResourceSet>,
    issuer_effective: Option<&AsResourceSet>,
    parent_asnum_intervals: Option<&[(u32, u32)]>,
    parent_rdi_intervals: Option<&[(u32, u32)]>,
) -> Result<(Option<AsResourceSet>, bool, bool), CaPathError> {
    let Some(child_as) = child_as else {
        return Ok((None, false, false));
    };
    if issuer_effective.is_none()
        && (matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit))
            || matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit)))
    {
        return Err(CaPathError::InheritWithoutParentResources);
    }

    let (asnum, asnum_reduced, asnum_empty) = resolve_as_choice_vrs(
        child_as.asnum.as_ref(),
        issuer_effective.and_then(|p| p.asnum.as_ref()),
        parent_asnum_intervals,
    )?;
    let (rdi, rdi_reduced, rdi_empty) = resolve_as_choice_vrs(
        child_as.rdi.as_ref(),
        issuer_effective.and_then(|p| p.rdi.as_ref()),
        parent_rdi_intervals,
    )?;
    Ok((
        Some(AsResourceSet { asnum, rdi }),
        asnum_reduced || rdi_reduced,
        asnum_empty || rdi_empty,
    ))
}

fn resolve_as_choice_vrs(
    child: Option<&AsIdentifierChoice>,
    parent: Option<&AsIdentifierChoice>,
    parent_intervals_hint: Option<&[(u32, u32)]>,
) -> Result<(Option<AsIdentifierChoice>, bool, bool), CaPathError> {
    let Some(child) = child else {
        return Ok((None, false, false));
    };
    match child {
        AsIdentifierChoice::Inherit => {
            let parent = parent
                .cloned()
                .ok_or(CaPathError::InheritWithoutParentResources)?;
            Ok((Some(parent), false, false))
        }
        AsIdentifierChoice::AsIdsOrRanges(_) => {
            let child_intervals = as_choice_to_merged_intervals(child);
            let parent_intervals;
            let parent_intervals = match parent_intervals_hint {
                Some(v) => v,
                None => {
                    parent_intervals = parent
                        .map(as_choice_to_merged_intervals)
                        .unwrap_or_default();
                    parent_intervals.as_slice()
                }
            };
            let intersections = intersect_as_intervals(&child_intervals, parent_intervals);
            let reduced = child_intervals != intersections;
            let empty = !child_intervals.is_empty() && intersections.is_empty();
            Ok((
                Some(AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(
                    &intersections,
                ))),
                reduced,
                empty,
            ))
        }
    }
}

fn as_choice_subset(
    child: Option<&AsIdentifierChoice>,
    parent: Option<&AsIdentifierChoice>,
) -> bool {
    as_choice_subset_with_parent_intervals(child, parent, None)
}

fn as_choice_subset_with_parent_intervals(
    child: Option<&AsIdentifierChoice>,
    parent: Option<&AsIdentifierChoice>,
    parent_intervals_hint: Option<&[(u32, u32)]>,
) -> bool {
    let Some(child) = child else {
        return true;
    };
    let Some(parent) = parent else {
        return false;
    };

    // Treat inherit as "all of parent" here; actual resolution is handled elsewhere.
    if matches!(child, AsIdentifierChoice::Inherit) {
        return true;
    }
    if matches!(parent, AsIdentifierChoice::Inherit) {
        return true;
    }

    let child_intervals = as_choice_to_merged_intervals(child);
    let owned_parent_intervals;
    let parent_intervals = match parent_intervals_hint {
        Some(intervals) => intervals,
        None => {
            owned_parent_intervals = as_choice_to_merged_intervals(parent);
            owned_parent_intervals.as_slice()
        }
    };
    for (cmin, cmax) in &child_intervals {
        if !as_interval_is_covered(parent_intervals, *cmin, *cmax) {
            return false;
        }
    }
    true
}

fn as_choice_to_merged_intervals(choice: &AsIdentifierChoice) -> Vec<(u32, u32)> {
    let mut v = Vec::new();
    match choice {
        AsIdentifierChoice::Inherit => {}
        AsIdentifierChoice::AsIdsOrRanges(items) => {
            for item in items {
                match item {
                    crate::data_model::rc::AsIdOrRange::Id(id) => v.push((*id, *id)),
                    crate::data_model::rc::AsIdOrRange::Range { min, max } => v.push((*min, *max)),
                }
            }
        }
    }
    v.sort_by_key(|(a, _b)| *a);
    merge_as_intervals(&v)
}

fn merge_as_intervals(v: &[(u32, u32)]) -> Vec<(u32, u32)> {
    let mut out: Vec<(u32, u32)> = Vec::new();
    for (min, max) in v {
        let Some(last) = out.last_mut() else {
            out.push((*min, *max));
            continue;
        };
        if *min <= last.1.saturating_add(1) {
            last.1 = last.1.max(*max);
            continue;
        }
        out.push((*min, *max));
    }
    out
}

fn as_interval_is_covered(parent: &[(u32, u32)], min: u32, max: u32) -> bool {
    for (pmin, pmax) in parent {
        if *pmin <= min && max <= *pmax {
            return true;
        }
        if *pmin > min {
            break;
        }
    }
    false
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum AfiKey {
    V4,
    V6,
}

fn ip_resources_by_afi_items(
    set: &IpResourceSet,
) -> Result<
    std::collections::BTreeMap<
        crate::data_model::rc::Afi,
        Vec<crate::data_model::rc::IpAddressOrRange>,
    >,
    CaPathError,
> {
    let mut m: std::collections::BTreeMap<
        crate::data_model::rc::Afi,
        Vec<crate::data_model::rc::IpAddressOrRange>,
    > = std::collections::BTreeMap::new();
    for fam in &set.families {
        match &fam.choice {
            IpAddressChoice::Inherit => return Err(CaPathError::InheritWithoutParentResources),
            IpAddressChoice::AddressesOrRanges(items) => {
                m.insert(fam.afi, items.clone());
            }
        }
    }
    Ok(m)
}

fn ip_resources_single_afi(
    parent: &IpResourceSet,
    afi: crate::data_model::rc::Afi,
    items: Option<&Vec<crate::data_model::rc::IpAddressOrRange>>,
) -> IpResourceSet {
    let mut families = Vec::new();
    if let Some(items) = items {
        families.push(crate::data_model::rc::IpAddressFamily {
            afi,
            choice: IpAddressChoice::AddressesOrRanges(items.clone()),
        });
    } else {
        // If parent doesn't mention this AFI explicitly, treat as empty.
        // The subset check will fail.
        let _ = parent;
    }
    IpResourceSet { families }
}

fn ip_family_items_subset(
    child_items: &[crate::data_model::rc::IpAddressOrRange],
    parent_set: &IpResourceSet,
) -> bool {
    let parent_by_afi = ip_resources_to_merged_intervals(parent_set);
    // parent_set should contain exactly one AFI.
    let (afi_key, parent_intervals) = match parent_by_afi.into_iter().next() {
        None => return false,
        Some(v) => v,
    };

    let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
    for item in child_items {
        match item {
            crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
                child_intervals.push(prefix_to_range(p))
            }
            crate::data_model::rc::IpAddressOrRange::Range(r) => {
                child_intervals.push((r.min.clone(), r.max.clone()))
            }
        }
    }
    child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
    let child_intervals = merge_ip_intervals(&child_intervals);

    let _ = afi_key;
    for (cmin, cmax) in &child_intervals {
        if !interval_is_covered(&parent_intervals, cmin, cmax) {
            return false;
        }
    }
    true
}

fn ip_resources_to_merged_intervals(
    set: &IpResourceSet,
) -> std::collections::HashMap<AfiKey, Vec<(Vec<u8>, Vec<u8>)>> {
    let m = ip_resources_to_merged_intervals_by_afi(set);
    m.into_iter()
        .map(|(afi, ranges)| {
            (
                match afi {
                    crate::data_model::rc::Afi::Ipv4 => AfiKey::V4,
                    crate::data_model::rc::Afi::Ipv6 => AfiKey::V6,
                },
                ranges,
            )
        })
        .collect()
}

fn ip_resources_to_merged_intervals_by_afi(
    set: &IpResourceSet,
) -> HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>> {
    let mut m: HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>> = HashMap::new();

    for fam in &set.families {
        match &fam.choice {
            IpAddressChoice::Inherit => {
                // When used in subset checks, treat inherit as "all" by leaving it absent.
                // Resolution should have happened earlier.
            }
            IpAddressChoice::AddressesOrRanges(items) => {
                let ent = m.entry(fam.afi).or_default();
                for item in items {
                    match item {
                        crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
                            let (min, max) = prefix_to_range(p);
                            ent.push((min, max));
                        }
                        crate::data_model::rc::IpAddressOrRange::Range(r) => {
                            ent.push((r.min.clone(), r.max.clone()));
                        }
                    }
                }
            }
        }
    }

    for (_afi, v) in m.iter_mut() {
        v.sort_by(|(a, _), (b, _)| a.cmp(b));
        *v = merge_ip_intervals(v);
    }

    m
}

fn ip_family_items_subset_with_parent_intervals(
    child_items: &[crate::data_model::rc::IpAddressOrRange],
    parent_intervals: &[(Vec<u8>, Vec<u8>)],
) -> bool {
    if parent_intervals.is_empty() {
        return false;
    }

    let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
    for item in child_items {
        match item {
            crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
                child_intervals.push(prefix_to_range(p))
            }
            crate::data_model::rc::IpAddressOrRange::Range(r) => {
                child_intervals.push((r.min.clone(), r.max.clone()))
            }
        }
    }
    child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
    let child_intervals = merge_ip_intervals(&child_intervals);

    for (cmin, cmax) in &child_intervals {
        if !interval_is_covered(parent_intervals, cmin, cmax) {
            return false;
        }
    }
    true
}

fn ip_items_to_merged_intervals(
    items: &[crate::data_model::rc::IpAddressOrRange],
) -> Vec<(Vec<u8>, Vec<u8>)> {
    let mut intervals = Vec::new();
    for item in items {
        match item {
            crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
                intervals.push(prefix_to_range(p))
            }
            crate::data_model::rc::IpAddressOrRange::Range(r) => {
                intervals.push((r.min.clone(), r.max.clone()))
            }
        }
    }
    intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
    merge_ip_intervals(&intervals)
}

fn intersect_ip_items_with_parent_intervals(
    items: &[crate::data_model::rc::IpAddressOrRange],
    parent_intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<(Vec<u8>, Vec<u8>)> {
    let child_intervals = ip_items_to_merged_intervals(items);
    intersect_ip_intervals(&child_intervals, parent_intervals)
}

fn intersect_ip_intervals(
    child_intervals: &[(Vec<u8>, Vec<u8>)],
    parent_intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<(Vec<u8>, Vec<u8>)> {
    let mut out = Vec::new();
    let mut parent_index = 0usize;
    for (child_min, child_max) in child_intervals {
        while parent_index < parent_intervals.len()
            && parent_intervals[parent_index].1.as_slice() < child_min.as_slice()
        {
            parent_index += 1;
        }
        let mut scan = parent_index;
        while scan < parent_intervals.len()
            && parent_intervals[scan].0.as_slice() <= child_max.as_slice()
        {
            let (parent_min, parent_max) = &parent_intervals[scan];
            let min = if bytes_leq(child_min, parent_min) {
                parent_min.clone()
            } else {
                child_min.clone()
            };
            let max = if bytes_leq(child_max, parent_max) {
                child_max.clone()
            } else {
                parent_max.clone()
            };
            if bytes_leq(&min, &max) {
                out.push((min, max));
            }
            scan += 1;
        }
    }
    merge_ip_intervals(&out)
}

fn ip_intervals_to_ranges(
    afi: crate::data_model::rc::Afi,
    intervals: &[(Vec<u8>, Vec<u8>)],
) -> Vec<crate::data_model::rc::IpAddressOrRange> {
    intervals
        .iter()
        .map(|(min, max)| {
            crate::data_model::rc::IpAddressOrRange::Range(crate::data_model::rc::IpAddressRange {
                min: normalize_ip_bytes(afi, min),
                max: normalize_ip_bytes(afi, max),
            })
        })
        .collect()
}

fn normalize_ip_bytes(afi: crate::data_model::rc::Afi, bytes: &[u8]) -> Vec<u8> {
    let target_len = afi.octets_len();
    if bytes.len() == target_len {
        return bytes.to_vec();
    }
    let mut out = vec![0u8; target_len];
    let copy_len = bytes.len().min(target_len);
    out[..copy_len].copy_from_slice(&bytes[..copy_len]);
    out
}

fn intervals_changed<T: PartialEq>(left: &[T], right: &[T]) -> bool {
    left != right
}

fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> {
    let mut out = Vec::new();
    let mut parent_index = 0usize;
    for (child_min, child_max) in child {
        while parent_index < parent.len() && parent[parent_index].1 < *child_min {
            parent_index += 1;
        }
        let mut scan = parent_index;
        while scan < parent.len() && parent[scan].0 <= *child_max {
            let min = (*child_min).max(parent[scan].0);
            let max = (*child_max).min(parent[scan].1);
            if min <= max {
                out.push((min, max));
            }
            scan += 1;
        }
    }
    merge_as_intervals(&out)
}

fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec<crate::data_model::rc::AsIdOrRange> {
    intervals
        .iter()
        .map(|(min, max)| {
            if min == max {
                crate::data_model::rc::AsIdOrRange::Id(*min)
            } else {
                crate::data_model::rc::AsIdOrRange::Range {
                    min: *min,
                    max: *max,
                }
            }
        })
        .collect()
}

fn merge_ip_intervals(v: &[(Vec<u8>, Vec<u8>)]) -> Vec<(Vec<u8>, Vec<u8>)> {
    let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
    for (min, max) in v {
        let Some(last) = out.last_mut() else {
            out.push((min.clone(), max.clone()));
            continue;
        };

        if bytes_leq(min, &increment_bytes(&last.1)) {
            if bytes_leq(&last.1, max) {
                last.1 = max.clone();
            }
            continue;
        }

        out.push((min.clone(), max.clone()));
    }
    out
}

fn interval_is_covered(parent: &[(Vec<u8>, Vec<u8>)], min: &[u8], max: &[u8]) -> bool {
    for (pmin, pmax) in parent {
        if bytes_leq(pmin, min) && bytes_leq(max, pmax) {
            return true;
        }
        if pmin.as_slice() > min {
            break;
        }
    }
    false
}

fn prefix_to_range(prefix: &crate::data_model::rc::IpPrefix) -> (Vec<u8>, Vec<u8>) {
    let mut min = prefix.addr.clone();
    let mut max = prefix.addr.clone();

    let bitlen = match prefix.afi {
        crate::data_model::rc::Afi::Ipv4 => 32u16,
        crate::data_model::rc::Afi::Ipv6 => 128u16,
    };
    let plen = prefix.prefix_len.min(bitlen);
    for bit in plen..bitlen {
        let byte = (bit / 8) as usize;
        let offset = 7 - (bit % 8);
        let mask = 1u8 << offset;
        min[byte] &= !mask;
        max[byte] |= mask;
    }
    (min, max)
}

fn bytes_leq(a: &[u8], b: &[u8]) -> bool {
    a <= b
}

#[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::{
        Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
        IpAddressOrRange, IpPrefix, IpResourceSet,
    };
    use crate::data_model::rc::{
        BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
        ResourceCertificate, RpkixTbsCertificate,
    };
    use der_parser::num_bigint::BigUint;
    use std::process::Command;
    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,
                },
            },
        }
    }

    fn openssl_available() -> bool {
        Command::new("openssl")
            .arg("version")
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false)
    }

    fn write_cert_der_with_addext(dir: &std::path::Path, addext: Option<&str>) -> Vec<u8> {
        assert!(openssl_available(), "openssl is required for this test");
        let key = dir.join("k.pem");
        let cert = dir.join("c.pem");
        let der = dir.join("c.der");

        let mut cmd = Command::new("openssl");
        cmd.arg("req")
            .arg("-x509")
            .arg("-newkey")
            .arg("rsa:2048")
            .arg("-nodes")
            .arg("-keyout")
            .arg(&key)
            .arg("-subj")
            .arg("/CN=ku")
            .arg("-days")
            .arg("1")
            .arg("-out")
            .arg(&cert);
        if let Some(ext) = addext {
            cmd.arg("-addext").arg(ext);
        }
        let out = cmd.output().expect("openssl req");
        assert!(
            out.status.success(),
            "openssl req failed: {}",
            String::from_utf8_lossy(&out.stderr)
        );

        let out = Command::new("openssl")
            .arg("x509")
            .arg("-in")
            .arg(&cert)
            .arg("-outform")
            .arg("DER")
            .arg("-out")
            .arg(&der)
            .output()
            .expect("openssl x509");
        assert!(
            out.status.success(),
            "openssl x509 failed: {}",
            String::from_utf8_lossy(&out.stderr)
        );
        std::fs::read(&der).expect("read der")
    }

    fn gen_issuer_and_child_der(dir: &std::path::Path) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
        assert!(openssl_available(), "openssl is required for this test");
        let issuer_key = dir.join("issuer.key");
        let issuer_csr = dir.join("issuer.csr");
        let issuer_pem = dir.join("issuer.pem");
        let issuer_der = dir.join("issuer.der");

        let child_key = dir.join("child.key");
        let child_csr = dir.join("child.csr");
        let child_pem = dir.join("child.pem");
        let child_der = dir.join("child.der");

        let other_key = dir.join("other.key");
        let other_csr = dir.join("other.csr");
        let other_pem = dir.join("other.pem");
        let other_der = dir.join("other.der");

        let run = |cmd: &mut Command| {
            let out = cmd.output().expect("run openssl");
            assert!(
                out.status.success(),
                "command failed: {:?}\nstderr={}",
                cmd,
                String::from_utf8_lossy(&out.stderr)
            );
        };

        // Issuer self-signed.
        run(Command::new("openssl")
            .args(["genrsa", "-out"])
            .arg(&issuer_key)
            .arg("2048"));
        run(Command::new("openssl")
            .args(["req", "-new", "-key"])
            .arg(&issuer_key)
            .args(["-subj", "/CN=issuer", "-out"])
            .arg(&issuer_csr));
        run(Command::new("openssl")
            .args(["x509", "-req", "-in"])
            .arg(&issuer_csr)
            .args(["-signkey"])
            .arg(&issuer_key)
            .args(["-days", "1", "-out"])
            .arg(&issuer_pem));
        run(Command::new("openssl")
            .args(["x509", "-in"])
            .arg(&issuer_pem)
            .args(["-outform", "DER", "-out"])
            .arg(&issuer_der));

        // Child signed by issuer.
        run(Command::new("openssl")
            .args(["genrsa", "-out"])
            .arg(&child_key)
            .arg("2048"));
        run(Command::new("openssl")
            .args(["req", "-new", "-key"])
            .arg(&child_key)
            .args(["-subj", "/CN=child", "-out"])
            .arg(&child_csr));
        run(Command::new("openssl")
            .args(["x509", "-req", "-in"])
            .arg(&child_csr)
            .args(["-CA"])
            .arg(&issuer_pem)
            .args(["-CAkey"])
            .arg(&issuer_key)
            .args(["-CAcreateserial", "-days", "1", "-out"])
            .arg(&child_pem));
        run(Command::new("openssl")
            .args(["x509", "-in"])
            .arg(&child_pem)
            .args(["-outform", "DER", "-out"])
            .arg(&child_der));

        // Other self-signed issuer.
        run(Command::new("openssl")
            .args(["genrsa", "-out"])
            .arg(&other_key)
            .arg("2048"));
        run(Command::new("openssl")
            .args(["req", "-new", "-key"])
            .arg(&other_key)
            .args(["-subj", "/CN=other", "-out"])
            .arg(&other_csr));
        run(Command::new("openssl")
            .args(["x509", "-req", "-in"])
            .arg(&other_csr)
            .args(["-signkey"])
            .arg(&other_key)
            .args(["-days", "1", "-out"])
            .arg(&other_pem));
        run(Command::new("openssl")
            .args(["x509", "-in"])
            .arg(&other_pem)
            .args(["-outform", "DER", "-out"])
            .arg(&other_der));

        (
            std::fs::read(&issuer_der).expect("read issuer der"),
            std::fs::read(&child_der).expect("read child der"),
            std::fs::read(&other_der).expect("read other der"),
        )
    }

    #[test]
    fn resolve_child_ip_resources_rejects_inherit_without_parent_effective_resources() {
        let child = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::Inherit,
            }],
        };
        let err = resolve_child_ip_resources(Some(&child), None).unwrap_err();
        assert!(matches!(err, CaPathError::InheritWithoutParentResources));
    }

    #[test]
    fn resolve_child_ip_resources_rejects_non_inherit_without_parent_effective_resources() {
        let child = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![]),
            }],
        };
        let err = resolve_child_ip_resources(Some(&child), None).unwrap_err();
        assert!(matches!(err, CaPathError::ResourcesNotSubset));
    }

    #[test]
    fn ip_resources_by_afi_items_rejects_inherit_families() {
        let parent = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv6,
                choice: IpAddressChoice::Inherit,
            }],
        };
        let err = ip_resources_by_afi_items(&parent).unwrap_err();
        assert!(matches!(err, CaPathError::InheritWithoutParentResources));
    }

    #[test]
    fn resolve_child_as_resources_rejects_inherit_without_parent_effective_resources() {
        let child = AsResourceSet {
            asnum: Some(AsIdentifierChoice::Inherit),
            rdi: None,
        };
        let err = resolve_child_as_resources(Some(&child), None).unwrap_err();
        assert!(matches!(err, CaPathError::InheritWithoutParentResources));
    }

    #[test]
    fn validation_update_03_intersects_overclaiming_child_ip_resources() {
        let issuer = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };
        let child = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 23,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };
        let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
            .expect("index");
        let resolved = resolve_child_resources(
            Some(&child),
            Some(&issuer),
            None,
            None,
            &index,
            ResourceValidationMode::ValidationUpdate03,
        )
        .expect("vrs resolution");
        assert!(resolved.warnings.ip_reduced_by_vrs);
        assert!(!resolved.warnings.ip_vrs_empty);
        let effective = resolved.effective_ip_resources.expect("effective ip");
        assert!(effective.families[0].contains_prefix(&IpPrefix {
            afi: Afi::Ipv4,
            prefix_len: 24,
            addr: vec![10, 0, 0, 0],
        }));
        assert!(!effective.families[0].contains_prefix(&IpPrefix {
            afi: Afi::Ipv4,
            prefix_len: 24,
            addr: vec![10, 0, 1, 0],
        }));
    }

    #[test]
    fn validation_update_03_keeps_empty_vrs_instead_of_rejecting_child_ca() {
        let issuer = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };
        let child = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![192, 0, 2, 0],
                    },
                )]),
            }],
        };
        let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
            .expect("index");
        let strict_err = resolve_child_resources(
            Some(&child),
            Some(&issuer),
            None,
            None,
            &index,
            ResourceValidationMode::Rfc6487,
        )
        .unwrap_err();
        assert!(matches!(strict_err, CaPathError::ResourcesNotSubset));

        let resolved = resolve_child_resources(
            Some(&child),
            Some(&issuer),
            None,
            None,
            &index,
            ResourceValidationMode::ValidationUpdate03,
        )
        .expect("vrs resolution");
        assert!(resolved.warnings.ip_reduced_by_vrs);
        assert!(resolved.warnings.ip_vrs_empty);
        assert!(
            resolved
                .effective_ip_resources
                .expect("effective ip")
                .families
                .is_empty()
        );
    }

    #[test]
    fn validation_update_03_intersects_overclaiming_child_as_resources() {
        let issuer = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64500,
                    max: 64510,
                },
            ])),
            rdi: None,
        };
        let child = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64505,
                    max: 64520,
                },
            ])),
            rdi: None,
        };
        let index = IssuerEffectiveResourcesIndex::from_effective_resources(None, Some(&issuer))
            .expect("index");
        let resolved = resolve_child_resources(
            None,
            None,
            Some(&child),
            Some(&issuer),
            &index,
            ResourceValidationMode::ValidationUpdate03,
        )
        .expect("vrs resolution");
        assert!(resolved.warnings.as_reduced_by_vrs);
        assert!(!resolved.warnings.as_vrs_empty);
        let effective = resolved.effective_as_resources.expect("effective as");
        assert_eq!(
            effective.asnum,
            Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64505,
                    max: 64510,
                },
            ]))
        );
    }

    #[test]
    fn validation_update_03_records_all_resource_warning_summary_parts() {
        let issuer_ip = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };
        let child_ip = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![192, 0, 2, 0],
                    },
                )]),
            }],
        };
        let issuer_as = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64500,
                    max: 64510,
                },
            ])),
            rdi: None,
        };
        let child_as = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64520,
                    max: 64530,
                },
            ])),
            rdi: None,
        };
        let index = IssuerEffectiveResourcesIndex::from_effective_resources(
            Some(&issuer_ip),
            Some(&issuer_as),
        )
        .expect("index");

        let resolved = resolve_child_resources(
            Some(&child_ip),
            Some(&issuer_ip),
            Some(&child_as),
            Some(&issuer_as),
            &index,
            ResourceValidationMode::ValidationUpdate03,
        )
        .expect("vrs resolution");

        assert!(resolved.warnings.ip_reduced_by_vrs);
        assert!(resolved.warnings.ip_vrs_empty);
        assert!(resolved.warnings.as_reduced_by_vrs);
        assert!(resolved.warnings.as_vrs_empty);
        assert_eq!(
            resolved.warnings.summary(),
            "ip_reduced_by_vrs,as_reduced_by_vrs,ip_vrs_empty,as_vrs_empty"
        );
        assert!(!resolved.warnings.is_empty());
    }

    #[test]
    fn child_aki_mismatch_is_rejected() {
        let issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "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_child_aki_matches_issuer_ski(&child, &issuer).unwrap_err();
        assert!(matches!(err, CaPathError::ChildAkiMismatch), "{err}");
    }

    #[test]
    fn child_aia_missing_is_rejected() {
        let _issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            None,
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err =
            validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer")
                .unwrap_err();
        assert!(matches!(err, CaPathError::ChildAiaMissing), "{err}");

        // Also cover issuer ski missing.
        let issuer_missing_ski = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            None,
            None,
            None,
            None,
        );
        let err = validate_child_aki_matches_issuer_ski(&child, &issuer_missing_ski).unwrap_err();
        assert!(matches!(err, CaPathError::IssuerSkiMissing), "{err}");
    }

    #[test]
    fn child_aia_issuer_uri_mismatch_is_rejected() {
        let _issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/other.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err =
            validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer")
                .unwrap_err();
        assert!(
            matches!(err, CaPathError::ChildAiaIssuerUriMismatch),
            "{err}"
        );
    }

    #[test]
    fn child_crldp_mismatch_is_rejected() {
        let issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            None,
        );
        let err =
            validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
                .unwrap_err();
        assert!(matches!(err, CaPathError::ChildCrlDpMissing), "{err}");

        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "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_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
                .unwrap_err();
        assert!(matches!(err, CaPathError::ChildCrlDpUriMismatch), "{err}");

        // Cover child AKI missing.
        let child_missing_aki = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "CN=issuer",
            Some(vec![2]),
            None,
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        let err = validate_child_aki_matches_issuer_ski(&child_missing_aki, &issuer).unwrap_err();
        assert!(matches!(err, CaPathError::ChildAkiMissing), "{err}");
    }

    #[test]
    fn child_binding_checks_accept_when_matching() {
        let issuer = dummy_cert(
            ResourceCertKind::Ca,
            "CN=issuer",
            "CN=issuer",
            Some(vec![1]),
            None,
            None,
            None,
        );
        let child = dummy_cert(
            ResourceCertKind::Ca,
            "CN=child",
            "CN=issuer",
            Some(vec![2]),
            Some(vec![1]),
            Some(vec!["rsync://example.test/issuer.cer"]),
            Some(vec!["rsync://example.test/issuer.crl"]),
        );
        validate_child_aki_matches_issuer_ski(&child, &issuer).expect("aki ok");
        validate_child_aia_points_to_issuer_uri(&child, "rsync://example.test/issuer.cer")
            .expect("aia ok");
        validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
            .expect("crldp ok");
    }

    #[test]
    fn validate_child_ca_key_usage_accepts_only_keycertsign_and_crlsign_critical() {
        let td = tempfile::tempdir().expect("tempdir");
        let der = write_cert_der_with_addext(
            td.path(),
            Some("keyUsage = critical, keyCertSign, cRLSign"),
        );
        let cert = parse_x509_cert(&der).expect("x509 parse ok");
        validate_child_ca_key_usage(&cert).expect("key usage ok");
    }

    #[test]
    fn validate_child_ca_key_usage_rejects_missing_noncritical_and_invalid_bits() {
        let td = tempfile::tempdir().expect("tempdir");
        let missing = write_cert_der_with_addext(td.path(), None);
        let cert = parse_x509_cert(&missing).expect("x509 parse ok");
        let err = validate_child_ca_key_usage(&cert).unwrap_err();
        assert!(matches!(err, CaPathError::KeyUsageMissing), "{err}");

        let td = tempfile::tempdir().expect("tempdir");
        let noncritical =
            write_cert_der_with_addext(td.path(), Some("keyUsage = keyCertSign, cRLSign"));
        let cert = parse_x509_cert(&noncritical).expect("x509 parse ok");
        let err = validate_child_ca_key_usage(&cert).unwrap_err();
        assert!(matches!(err, CaPathError::KeyUsageNotCritical), "{err}");

        let td = tempfile::tempdir().expect("tempdir");
        let invalid = write_cert_der_with_addext(
            td.path(),
            Some("keyUsage = critical, keyCertSign, cRLSign, digitalSignature"),
        );
        let cert = parse_x509_cert(&invalid).expect("x509 parse ok");
        let err = validate_child_ca_key_usage(&cert).unwrap_err();
        assert!(matches!(err, CaPathError::KeyUsageInvalidBits), "{err}");
    }

    #[test]
    fn verify_cert_signature_with_issuer_accepts_valid_chain_and_rejects_wrong_issuer() {
        let td = tempfile::tempdir().expect("tempdir");
        let (issuer, child, other) = gen_issuer_and_child_der(td.path());
        let issuer_cert = parse_x509_cert(&issuer).expect("x509 parse issuer");
        let child_cert = parse_x509_cert(&child).expect("x509 parse child");
        verify_child_signature(&child_cert, &issuer_cert.tbs_certificate.subject_pki)
            .expect("signature ok");
        let other_cert = parse_x509_cert(&other).expect("x509 parse other");
        let err = verify_child_signature(&child_cert, &other_cert.tbs_certificate.subject_pki)
            .unwrap_err();
        assert!(
            matches!(err, CaPathError::ChildSignatureInvalid(_)),
            "{err}"
        );
    }

    #[test]
    fn signature_cache_stores_real_verify_success_and_skips_on_hit() {
        let _guard = crate::crypto_sig_cache::GLOBAL_TEST_LOCK.lock().expect("test lock");
        crate::crypto_sig_cache::clear_global();
        let td = tempfile::tempdir().expect("tempdir");
        let (issuer, child, other) = gen_issuer_and_child_der(td.path());
        let issuer_cert = parse_x509_cert(&issuer).expect("x509 parse issuer");
        let child_cert = parse_x509_cert(&child).expect("x509 parse child");
        let other_cert = parse_x509_cert(&other).expect("x509 parse other");

        let cache_dir = tempfile::tempdir().expect("tempdir");
        let cache = std::sync::Arc::new(crate::crypto_sig_cache::CryptoSigCache::load_or_rebuild(
            cache_dir.path().join("work-db.crypto-sig-cache"),
            true,
        ));
        crate::crypto_sig_cache::install_global(std::sync::Arc::clone(&cache));

        // Miss -> real verification runs and passes -> positive conclusion written.
        verify_child_signature(&child_cert, &issuer_cert.tbs_certificate.subject_pki)
            .expect("signature ok");
        // Hit -> skipped (a second execution would also pass, so assert via stats).
        verify_child_signature(&child_cert, &issuer_cert.tbs_certificate.subject_pki)
            .expect("signature ok");
        // Wrong issuer: real verification fails, nothing is written (no negative caching),
        // so a retry executes the real verification again instead of inheriting anything.
        for _ in 0..2 {
            let err = verify_child_signature(&child_cert, &other_cert.tbs_certificate.subject_pki)
                .unwrap_err();
            assert!(
                matches!(err, CaPathError::ChildSignatureInvalid(_)),
                "{err}"
            );
        }

        let summary = cache.summary();
        let stats = summary
            .per_point
            .get("child_ca_cert")
            .expect("child_ca_cert stats");
        assert_eq!(stats.calls, 4);
        assert_eq!(stats.would_hit, 1);
        assert_eq!(stats.new_keys, 3);
        assert_eq!(stats.verify_executed, 3);
        assert_eq!(stats.verify_skipped, 1);
        crate::crypto_sig_cache::clear_global();
    }

    #[test]
    fn issuer_effective_resources_index_and_indexed_resolvers_cover_success_and_failure_paths() {
        use crate::data_model::rc::{AsIdOrRange, IpPrefix};

        let parent_ip = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 8,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };
        let parent_as = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range {
                    min: 64500,
                    max: 64599,
                },
            ])),
            rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                65000,
            )])),
        };
        let idx = IssuerEffectiveResourcesIndex::from_effective_resources(
            Some(&parent_ip),
            Some(&parent_as),
        )
        .expect("index builds");
        assert_eq!(
            idx.parent_ip_by_afi_items.as_ref().map(|v| v.len()),
            Some(1)
        );
        assert_eq!(idx.parent_ip_merged_intervals.len(), 1);
        assert_eq!(
            idx.parent_asnum_intervals.as_ref().map(|v| v.len()),
            Some(1)
        );
        assert_eq!(idx.parent_rdi_intervals.as_ref().map(|v| v.len()), Some(1));

        let child_ip_subset = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 16,
                        addr: vec![10, 1, 0, 0],
                    },
                )]),
            }],
        };
        assert!(
            resolve_child_ip_resources_indexed(
                Some(&child_ip_subset),
                Some(&parent_ip),
                idx.parent_ip_by_afi_items.as_ref(),
                &idx.parent_ip_merged_intervals,
            )
            .expect("subset should resolve")
            .is_some()
        );

        let child_ip_bad = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 16,
                        addr: vec![11, 0, 0, 0],
                    },
                )]),
            }],
        };
        let err = resolve_child_ip_resources_indexed(
            Some(&child_ip_bad),
            Some(&parent_ip),
            idx.parent_ip_by_afi_items.as_ref(),
            &idx.parent_ip_merged_intervals,
        )
        .unwrap_err();
        assert!(matches!(err, CaPathError::ResourcesNotSubset));

        let child_as_subset = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                64542,
            )])),
            rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                65000,
            )])),
        };
        assert!(
            resolve_child_as_resources_indexed(
                Some(&child_as_subset),
                Some(&parent_as),
                idx.parent_asnum_intervals.as_deref(),
                idx.parent_rdi_intervals.as_deref(),
            )
            .expect("subset as resolves")
            .is_some()
        );

        let child_as_bad = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                65123,
            )])),
            rdi: None,
        };
        let err = resolve_child_as_resources_indexed(
            Some(&child_as_bad),
            Some(&parent_as),
            idx.parent_asnum_intervals.as_deref(),
            idx.parent_rdi_intervals.as_deref(),
        )
        .unwrap_err();
        assert!(matches!(err, CaPathError::ResourcesNotSubset));
    }

    #[test]
    fn resolve_child_ip_and_as_resources_success_paths() {
        use crate::data_model::rc::{AsIdOrRange, IpAddressOrRange, IpPrefix};

        let parent_ip = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 8,
                        addr: vec![10, 0, 0, 0],
                    },
                )]),
            }],
        };

        let child_ip_inherit = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::Inherit,
            }],
        };
        let eff = resolve_child_ip_resources(Some(&child_ip_inherit), Some(&parent_ip))
            .expect("inherit resolves")
            .expect("some ip");
        assert_eq!(eff.families.len(), 1);
        assert!(matches!(
            eff.families[0].choice,
            IpAddressChoice::AddressesOrRanges(_)
        ));

        let child_ip_subset = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 16,
                        addr: vec![10, 1, 0, 0],
                    },
                )]),
            }],
        };
        resolve_child_ip_resources(Some(&child_ip_subset), Some(&parent_ip))
            .expect("subset ok")
            .expect("some");

        let child_ip_bad = IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 16,
                        addr: vec![11, 0, 0, 0],
                    },
                )]),
            }],
        };
        let err = resolve_child_ip_resources(Some(&child_ip_bad), Some(&parent_ip)).unwrap_err();
        assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}");

        let parent_as = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
                AsIdOrRange::Range { min: 1, max: 100 },
            ])),
            rdi: None,
        };
        let child_as_inherit = AsResourceSet {
            asnum: Some(AsIdentifierChoice::Inherit),
            rdi: None,
        };
        let eff_as = resolve_child_as_resources(Some(&child_as_inherit), Some(&parent_as))
            .expect("inherit as")
            .expect("some");
        assert_eq!(eff_as.asnum, parent_as.asnum);

        let child_as_subset = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(50)])),
            rdi: None,
        };
        resolve_child_as_resources(Some(&child_as_subset), Some(&parent_as))
            .expect("subset as")
            .expect("some");

        let child_as_bad = AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                200,
            )])),
            rdi: None,
        };
        let err = resolve_child_as_resources(Some(&child_as_bad), Some(&parent_as)).unwrap_err();
        assert!(matches!(err, CaPathError::ResourcesNotSubset), "{err}");
    }
}

fn increment_bytes(v: &[u8]) -> Vec<u8> {
    let mut out = v.to_vec();
    for i in (0..out.len()).rev() {
        if out[i] != 0xFF {
            out[i] += 1;
            for j in i + 1..out.len() {
                out[j] = 0;
            }
            return out;
        }
    }
    vec![0u8; out.len()]
}
