use der_parser::ber::{BerObjectContent, Class};
use der_parser::der::{DerObject, Tag, parse_der};
use der_parser::num_bigint::BigUint;
use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag};
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version};

use serde::{Deserialize, Serialize};

use crate::data_model::common::{
    Asn1TimeUtc, DerReader, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
};
use crate::data_model::oid::{
    OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
    OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
    OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS, OID_AUTHORITY_INFO_ACCESS_RAW,
    OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_AUTONOMOUS_SYS_IDS,
    OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS, OID_BASIC_CONSTRAINTS_RAW,
    OID_CERTIFICATE_POLICIES, OID_CERTIFICATE_POLICIES_RAW, OID_CP_IPADDR_ASNUMBER,
    OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS, OID_CRL_DISTRIBUTION_POINTS_RAW,
    OID_EXTENDED_KEY_USAGE, OID_IP_ADDR_BLOCKS, OID_IP_ADDR_BLOCKS_RAW, OID_KEY_USAGE, OID_QT_CPS,
    OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SUBJECT_INFO_ACCESS,
    OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER, OID_SUBJECT_KEY_IDENTIFIER_RAW,
};

/// Resource Certificate kind (semantic classification).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceCertKind {
    Ca,
    Ee,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceCertificateRole {
    TrustAnchor,
    Ca,
    SignedObjectEe,
    RouterEe,
}

/// A parsed RPKI Resource Certificate (RFC 6487) data model.
///
/// This module intentionally focuses on the semantics needed by Signed Object validation and
/// object-specific EE certificate checks (MFT/ROA/ASPA), as described in
/// `rpki/specs/03_resource_certificate_rc.md`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResourceCertificate {
    pub raw_der: Vec<u8>,
    pub tbs: RpkixTbsCertificate,
    pub kind: ResourceCertKind,
}

pub type ResourceCaCertificate = ResourceCertificate;
pub type ResourceEeCertificate = ResourceCertificate;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkixTbsCertificate {
    pub version: u32,
    pub serial_number: BigUint,
    pub signature_algorithm: String,
    pub issuer_name: X509NameDer,
    pub subject_name: X509NameDer,
    pub validity_not_before: UtcTime,
    pub validity_not_after: UtcTime,
    /// DER encoding of SubjectPublicKeyInfo.
    pub subject_public_key_info: Vec<u8>,
    pub extensions: RcExtensions,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RcExtensions {
    pub basic_constraints_ca: bool,
    pub basic_constraints: Option<BasicConstraintsProfile>,
    pub subject_key_identifier: Option<Vec<u8>>,
    /// Authority Key Identifier (AKI) keyIdentifier value.
    pub authority_key_identifier: Option<Vec<u8>>,
    /// CRL Distribution Points URIs (fullName).
    pub crl_distribution_points_uris: Option<Vec<String>>,
    /// Authority Information Access (AIA) caIssuers URIs.
    pub ca_issuers_uris: Option<Vec<String>>,
    pub subject_info_access: Option<SubjectInfoAccess>,
    pub certificate_policies_oid: Option<String>,
    pub certificate_policies: Option<CertificatePoliciesProfile>,
    pub extension_oids: Vec<String>,

    pub ip_resources: Option<IpResourceSet>,
    pub as_resources: Option<AsResourceSet>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BasicConstraintsProfile {
    pub ca: bool,
    pub critical: bool,
    pub path_len_constraint: Option<u32>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificatePoliciesProfile {
    pub policy_oid: String,
    pub qualifier_oids: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResourceCertificateParsed {
    pub raw_der: Vec<u8>,
    pub version: X509Version,
    pub serial_number: BigUint,
    pub signature_algorithm: AlgorithmIdentifierValue,
    pub tbs_signature_algorithm: AlgorithmIdentifierValue,
    pub issuer_name: X509NameDer,
    pub subject_name: X509NameDer,
    pub validity_not_before: Asn1TimeUtc,
    pub validity_not_after: Asn1TimeUtc,
    /// DER encoding of SubjectPublicKeyInfo.
    pub subject_public_key_info: Vec<u8>,
    pub extensions: RcExtensionsParsed,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgorithmIdentifierValue {
    pub oid: String,
    pub parameters: Option<AlgorithmParametersValue>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgorithmParametersValue {
    pub class: Asn1Class,
    pub tag: Asn1Tag,
    pub data: Vec<u8>,
}

impl AlgorithmIdentifierValue {
    pub fn params_absent_or_null(&self) -> bool {
        match &self.parameters {
            None => true,
            Some(p) if p.class == Asn1Class::Universal && p.tag == Asn1Tag::Null => true,
            Some(_p) => false,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RcExtensionsParsed {
    pub basic_constraints: Vec<BasicConstraintsProfile>,
    pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
    pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
    pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
    pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
    pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>,
    pub certificate_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)>,
    pub extension_oids: Vec<String>,
    pub ip_resources: Vec<(IpResourceSet, bool)>,
    pub as_resources: Vec<(AsResourceSet, bool)>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityKeyIdentifierParsed {
    pub key_identifier: Option<Vec<u8>>,
    pub has_authority_cert_issuer: bool,
    pub has_authority_cert_serial: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityInfoAccessParsed {
    pub ca_issuers_uris: Vec<String>,
    pub ca_issuers_access_location_not_uri: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrlDistributionPointsParsed {
    pub distribution_points: Vec<CrlDistributionPointParsed>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrlDistributionPointParsed {
    pub distribution_point_present: bool,
    pub reasons_present: bool,
    pub crl_issuer_present: bool,
    pub name_relative_to_crl_issuer_present: bool,
    pub full_name_uris: Vec<String>,
    pub full_name_not_uri: bool,
    pub full_name_present: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessParsed {
    pub access_descriptions: Vec<AccessDescription>,
    pub signed_object_uris: Vec<String>,
    pub signed_object_access_location_not_uri: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SubjectInfoAccess {
    Ca(SubjectInfoAccessCa),
    Ee(SubjectInfoAccessEe),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessCa {
    pub access_descriptions: Vec<AccessDescription>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessEe {
    pub signed_object_uris: Vec<String>,
    /// The full list of access descriptions as carried in the SIA extension.
    pub access_descriptions: Vec<AccessDescription>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccessDescription {
    pub access_method_oid: String,
    pub access_location: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Afi {
    Ipv4,
    Ipv6,
}

impl Afi {
    pub fn ub(self) -> u16 {
        match self {
            Afi::Ipv4 => 32,
            Afi::Ipv6 => 128,
        }
    }

    pub fn octets_len(self) -> usize {
        match self {
            Afi::Ipv4 => 4,
            Afi::Ipv6 => 16,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IpResourceSet {
    pub families: Vec<IpAddressFamily>,
}

impl IpResourceSet {
    /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for
    /// `id-pe-ipAddrBlocks` (RFC 3779 / RFC 6487).
    pub fn decode_extn_value(extn_value: &[u8]) -> Result<Self, IpResourceSetDecodeError> {
        parse_ip_addr_blocks(extn_value).map_err(|_| IpResourceSetDecodeError::InvalidEncoding)
    }

    pub fn is_all_inherit(&self) -> bool {
        self.families
            .iter()
            .all(|f| matches!(f.choice, IpAddressChoice::Inherit))
    }

    pub fn has_any_inherit(&self) -> bool {
        self.families
            .iter()
            .any(|f| matches!(f.choice, IpAddressChoice::Inherit))
    }

    pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool {
        self.families.iter().any(|fam| fam.contains_prefix(prefix))
    }
}

#[derive(Debug, thiserror::Error)]
pub enum IpResourceSetDecodeError {
    #[error("invalid ipAddrBlocks encoding (RFC 3779 §2.2.3; RFC 6487 §4.8.10)")]
    InvalidEncoding,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IpAddressFamily {
    pub afi: Afi,
    pub choice: IpAddressChoice,
}

impl IpAddressFamily {
    pub fn contains_prefix(&self, prefix: &IpPrefix) -> bool {
        if self.afi != prefix.afi {
            return false;
        }
        match &self.choice {
            IpAddressChoice::Inherit => true,
            IpAddressChoice::AddressesOrRanges(items) => items.iter().any(|item| match item {
                IpAddressOrRange::Prefix(p) => prefix_covers(p, prefix),
                IpAddressOrRange::Range(r) => range_covers_prefix(self.afi, r, prefix),
            }),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum IpAddressChoice {
    Inherit,
    AddressesOrRanges(Vec<IpAddressOrRange>),
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum IpAddressOrRange {
    Prefix(IpPrefix),
    Range(IpAddressRange),
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IpAddressRange {
    pub min: Vec<u8>,
    pub max: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct IpPrefix {
    pub afi: Afi,
    pub prefix_len: u16,
    pub addr: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsResourceSet {
    pub asnum: Option<AsIdentifierChoice>,
    pub rdi: Option<AsIdentifierChoice>,
}

impl AsResourceSet {
    /// Decode the DER bytes carried inside the X.509 `extnValue` OCTET STRING for
    /// `id-pe-autonomousSysIds` (RFC 3779 / RFC 6487).
    pub fn decode_extn_value(extn_value: &[u8]) -> Result<Self, AsResourceSetDecodeError> {
        parse_as_identifiers(extn_value).map_err(|_| AsResourceSetDecodeError::InvalidEncoding)
    }

    pub fn is_asnum_inherit(&self) -> bool {
        matches!(self.asnum, Some(AsIdentifierChoice::Inherit))
    }

    pub fn has_any_range(&self) -> bool {
        self.asnum.as_ref().map(|c| c.has_range()).unwrap_or(false)
            || self.rdi.as_ref().map(|c| c.has_range()).unwrap_or(false)
    }

    pub fn asnum_single_id(&self) -> Option<u32> {
        match self.asnum.as_ref()? {
            AsIdentifierChoice::Inherit => None,
            AsIdentifierChoice::AsIdsOrRanges(items) => {
                if items.len() != 1 {
                    return None;
                }
                match &items[0] {
                    AsIdOrRange::Id(v) => Some(*v),
                    AsIdOrRange::Range { .. } => None,
                }
            }
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum AsResourceSetDecodeError {
    #[error("invalid autonomousSysIds encoding (RFC 3779 §3.2.3; RFC 6487 §4.8.11)")]
    InvalidEncoding,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AsIdentifierChoice {
    Inherit,
    AsIdsOrRanges(Vec<AsIdOrRange>),
}

impl AsIdentifierChoice {
    pub fn has_range(&self) -> bool {
        match self {
            AsIdentifierChoice::Inherit => false,
            AsIdentifierChoice::AsIdsOrRanges(items) => {
                items.iter().any(|i| matches!(i, AsIdOrRange::Range { .. }))
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AsIdOrRange {
    Id(u32),
    Range { min: u32, max: u32 },
}

#[derive(Debug, thiserror::Error)]
pub enum ResourceCertificateParseError {
    #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 6487 §4)")]
    Parse(String),

    #[error("trailing bytes after certificate DER: {0} bytes (DER; RFC 5280 §4.1)")]
    TrailingBytes(usize),

    #[error("invalid RFC 3779 IP resources extension encoding (RFC 6487 §4.8.10; RFC 3779 §2.2)")]
    InvalidIpResourcesEncoding,

    #[error("invalid RFC 3779 AS resources extension encoding (RFC 6487 §4.8.11; RFC 3779 §3.2)")]
    InvalidAsResourcesEncoding,
}

#[derive(Debug, thiserror::Error)]
pub enum ResourceCertificateProfileError {
    #[error("{0}")]
    InvalidTimeEncoding(#[from] InvalidTimeEncodingError),

    #[error("certificate version must be v3 (RFC 5280 §4.1; RFC 6487 §4)")]
    InvalidVersion,

    #[error("signatureAlgorithm does not match tbsCertificate.signature (RFC 5280 §4.1)")]
    SignatureAlgorithmMismatch,

    #[error(
        "unsupported signature algorithm (expected sha256WithRSAEncryption {OID_SHA256_WITH_RSA_ENCRYPTION}) (RFC 7935 §2; RFC 6487 §4)"
    )]
    UnsupportedSignatureAlgorithm,

    #[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")]
    InvalidSignatureAlgorithmParameters,

    #[error(
        "{role} Name strict validation failed: {detail} (RFC 6487 §4.4; RFC 5280 §4.1.2.4/§4.1.2.6)"
    )]
    StrictName { role: &'static str, detail: String },

    #[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")]
    DuplicateExtension(&'static str),

    #[error("SubjectKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.2)")]
    SkiCriticality,

    #[error("SubjectInfoAccess criticality must be non-critical (RFC 6487 §4.8.8)")]
    SiaCriticality,

    #[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")]
    CertificatePoliciesCriticality,

    #[error("certificatePolicies must be present (RFC 6487 §4.8.9)")]
    CertificatePoliciesMissing,

    #[error(
        "certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
    )]
    InvalidCertificatePolicy(String),

    #[error("certificatePolicies may contain at most one CPS qualifier (RFC 6487 §4.8.9)")]
    CertificatePoliciesTooManyQualifiers,

    #[error(
        "certificatePolicies qualifier must be id-qt-cps ({OID_QT_CPS}), got {0} (RFC 6487 §4.8.9)"
    )]
    CertificatePoliciesInvalidQualifier(String),

    #[error("basicConstraints must be present in CA certificates (RFC 6487 §4.8.1)")]
    BasicConstraintsMissing,

    #[error("basicConstraints criticality must be critical in CA certificates (RFC 6487 §4.8.1)")]
    BasicConstraintsCriticality,

    #[error("basicConstraints cA must be TRUE in CA certificates (RFC 6487 §4.8.1)")]
    BasicConstraintsCaFalse,

    #[error(
        "basicConstraints pathLenConstraint must be absent in CA certificates (RFC 6487 §4.8.1)"
    )]
    BasicConstraintsPathLenPresent,

    #[error("basicConstraints must be absent in EE certificates (RFC 6487 §4.8.1)")]
    BasicConstraintsEeMustOmit,

    #[error("extension {oid} is not permitted for {role} resource certificates (RFC 6487 §4.8)")]
    DisallowedExtension { role: &'static str, oid: String },

    #[error("autonomousSysIds RDI field must be absent (RFC 6487 §4.8.11; RFC 3779 §3.2.3)")]
    AsResourcesRdiPresent,

    #[error(
        "SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
    )]
    SignedObjectSiaNotUri,

    #[error("SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)")]
    SignedObjectSiaNoRsync,

    #[error("ipAddrBlocks criticality must be critical when present (RFC 6487 §4.8.10)")]
    IpResourcesCriticality,

    #[error("autonomousSysIds criticality must be critical when present (RFC 6487 §4.8.11)")]
    AsResourcesCriticality,

    #[error(
        "authorityKeyIdentifier must be present in non-self-signed certificates (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
    )]
    AkiMissing,

    #[error(
        "authorityKeyIdentifier criticality must be non-critical (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
    )]
    AkiCriticality,

    #[error(
        "authorityKeyIdentifier authorityCertIssuer MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
    )]
    AkiAuthorityCertIssuerPresent,

    #[error(
        "authorityKeyIdentifier authorityCertSerialNumber MUST NOT be present (RFC 6487 §4.8.3; RFC 5280 §4.2.1.1)"
    )]
    AkiAuthorityCertSerialPresent,

    #[error(
        "self-signed certificate authorityKeyIdentifier must equal subjectKeyIdentifier when present (RFC 6487 §4.8.3)"
    )]
    AkiSelfSignedNotEqualSki,

    #[error(
        "CRLDistributionPoints must be present in non-self-signed certificates (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)"
    )]
    CrlDistributionPointsMissing,

    #[error(
        "CRLDistributionPoints criticality must be non-critical (RFC 6487 §4.8.6; RFC 5280 §4.2.1.13)"
    )]
    CrlDistributionPointsCriticality,

    #[error("CRLDistributionPoints MUST be omitted in self-signed certificates (RFC 6487 §4.8.6)")]
    CrlDistributionPointsSelfSignedMustOmit,

    #[error("CRLDistributionPoints must contain exactly one DistributionPoint (RFC 6487 §4.8.6)")]
    CrlDistributionPointsNotSingle,

    #[error("CRLDistributionPoints distributionPoint field MUST be present (RFC 6487 §4.8.6)")]
    CrlDistributionPointsNoDistributionPoint,

    #[error("CRLDistributionPoints reasons field MUST be omitted (RFC 6487 §4.8.6)")]
    CrlDistributionPointsHasReasons,

    #[error("CRLDistributionPoints cRLIssuer field MUST be omitted (RFC 6487 §4.8.6)")]
    CrlDistributionPointsHasCrlIssuer,

    #[error(
        "CRLDistributionPoints distributionPoint MUST contain fullName and MUST NOT contain nameRelativeToCRLIssuer (RFC 6487 §4.8.6)"
    )]
    CrlDistributionPointsInvalidName,

    #[error(
        "CRLDistributionPoints fullName must contain only URI GeneralNames (RFC 6487 §4.8.6; RFC 5280 §4.2.1.6)"
    )]
    CrlDistributionPointsFullNameNotUri,

    #[error("CRLDistributionPoints must include at least one rsync:// URI (RFC 6487 §4.8.6)")]
    CrlDistributionPointsNoRsync,

    #[error(
        "authorityInfoAccess must be present in non-self-signed certificates (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
    )]
    AuthorityInfoAccessMissing,

    #[error(
        "authorityInfoAccess criticality must be non-critical (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
    )]
    AuthorityInfoAccessCriticality,

    #[error("authorityInfoAccess MUST be omitted in self-signed certificates (RFC 6487 §4.8.7)")]
    AuthorityInfoAccessSelfSignedMustOmit,

    #[error(
        "authorityInfoAccess id-ad-caIssuers accessLocation must be URI (RFC 6487 §4.8.7; RFC 5280 §4.2.2.1)"
    )]
    AuthorityInfoAccessCaIssuersNotUri,

    #[error("authorityInfoAccess must include at least one id-ad-caIssuers URI (RFC 6487 §4.8.7)")]
    AuthorityInfoAccessMissingCaIssuers,

    #[error("authorityInfoAccess must include at least one rsync:// URI (RFC 6487 §4.8.7)")]
    AuthorityInfoAccessNoRsync,
}

#[derive(Debug, thiserror::Error)]
pub enum ResourceCertificateDecodeError {
    #[error("{0}")]
    Parse(#[from] ResourceCertificateParseError),

    #[error("{0}")]
    Validate(#[from] ResourceCertificateProfileError),
}

pub type ResourceCertificateError = ResourceCertificateDecodeError;

impl ResourceCertificate {
    /// Parse step of scheme A (`parse → validate → verify`).
    pub fn parse_der(
        der: &[u8],
    ) -> Result<ResourceCertificateParsed, ResourceCertificateParseError> {
        let (rem, cert) = X509Certificate::from_der(der)
            .map_err(|e| ResourceCertificateParseError::Parse(e.to_string()))?;
        if !rem.is_empty() {
            return Err(ResourceCertificateParseError::TrailingBytes(rem.len()));
        }

        let validity_not_before = asn1_time_to_model(cert.validity().not_before);
        let validity_not_after = asn1_time_to_model(cert.validity().not_after);

        let subject_public_key_info = cert.tbs_certificate.subject_pki.raw.to_vec();

        let signature_algorithm = algorithm_identifier_value(&cert.signature_algorithm);
        let tbs_signature_algorithm = algorithm_identifier_value(&cert.tbs_certificate.signature);
        let extensions = parse_extensions_parse(cert.extensions())?;

        Ok(ResourceCertificateParsed {
            raw_der: der.to_vec(),
            version: cert.version(),
            serial_number: cert.tbs_certificate.serial.clone(),
            signature_algorithm,
            tbs_signature_algorithm,
            issuer_name: X509NameDer(cert.issuer().as_raw().to_vec()),
            subject_name: X509NameDer(cert.subject().as_raw().to_vec()),
            validity_not_before,
            validity_not_after,
            subject_public_key_info,
            extensions,
        })
    }

    /// Profile validate step of scheme A (`parse → validate → verify`).
    ///
    /// `ResourceCertificate` is already profile-validated when constructed via `decode_der()` /
    /// `ResourceCertificateParsed::validate_profile()`.
    pub fn validate_profile(&self) -> Result<(), ResourceCertificateProfileError> {
        Ok(())
    }

    pub fn validate_rfc6487_profile(
        &self,
        role: ResourceCertificateRole,
    ) -> Result<(), ResourceCertificateProfileError> {
        let role_name = match role {
            ResourceCertificateRole::TrustAnchor => "trust anchor CA",
            ResourceCertificateRole::Ca => "CA",
            ResourceCertificateRole::SignedObjectEe => "signed-object EE",
            ResourceCertificateRole::RouterEe => "router EE",
        };
        let ca_role = matches!(
            role,
            ResourceCertificateRole::TrustAnchor | ResourceCertificateRole::Ca
        );

        if ca_role {
            let constraints = self
                .tbs
                .extensions
                .basic_constraints
                .as_ref()
                .ok_or(ResourceCertificateProfileError::BasicConstraintsMissing)?;
            if !constraints.critical {
                return Err(ResourceCertificateProfileError::BasicConstraintsCriticality);
            }
            if !constraints.ca {
                return Err(ResourceCertificateProfileError::BasicConstraintsCaFalse);
            }
            if constraints.path_len_constraint.is_some() {
                return Err(ResourceCertificateProfileError::BasicConstraintsPathLenPresent);
            }
        } else if self.tbs.extensions.basic_constraints.is_some() {
            return Err(ResourceCertificateProfileError::BasicConstraintsEeMustOmit);
        }

        for oid in &self.tbs.extensions.extension_oids {
            if !is_permitted_extension(oid, role) {
                return Err(ResourceCertificateProfileError::DisallowedExtension {
                    role: role_name,
                    oid: oid.clone(),
                });
            }
        }

        let policies = self
            .tbs
            .extensions
            .certificate_policies
            .as_ref()
            .ok_or(ResourceCertificateProfileError::CertificatePoliciesMissing)?;
        if policies.policy_oid != OID_CP_IPADDR_ASNUMBER {
            return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
                policies.policy_oid.clone(),
            ));
        }
        if policies.qualifier_oids.len() > 1 {
            return Err(ResourceCertificateProfileError::CertificatePoliciesTooManyQualifiers);
        }
        if let Some(qualifier_oid) = policies.qualifier_oids.first() {
            if qualifier_oid != OID_QT_CPS {
                return Err(
                    ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier(
                        qualifier_oid.clone(),
                    ),
                );
            }
        }

        if self
            .tbs
            .extensions
            .as_resources
            .as_ref()
            .is_some_and(|resources| resources.rdi.is_some())
        {
            return Err(ResourceCertificateProfileError::AsResourcesRdiPresent);
        }

        Ok(())
    }

    pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
        validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
        validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
        Ok(())
    }

    /// Decode a resource certificate (`parse + validate`).
    pub fn decode_der(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
        Ok(Self::parse_der(der)?.validate_profile()?)
    }

    pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
        let cert = Self::decode_der(der)?;
        cert.validate_strict_name_profile()?;
        Ok(cert)
    }

    /// Backwards-compatible helper (historical name).
    pub fn from_der(der: &[u8]) -> Result<Self, ResourceCertificateError> {
        Self::decode_der(der)
    }
}

fn is_permitted_extension(oid: &str, role: ResourceCertificateRole) -> bool {
    matches!(
        oid,
        OID_BASIC_CONSTRAINTS
            | OID_KEY_USAGE
            | OID_SUBJECT_KEY_IDENTIFIER
            | OID_AUTHORITY_KEY_IDENTIFIER
            | OID_CRL_DISTRIBUTION_POINTS
            | OID_AUTHORITY_INFO_ACCESS
            | OID_SUBJECT_INFO_ACCESS
            | OID_CERTIFICATE_POLICIES
            | OID_IP_ADDR_BLOCKS
            | OID_AUTONOMOUS_SYS_IDS
    ) || (role == ResourceCertificateRole::RouterEe && oid == OID_EXTENDED_KEY_USAGE)
}

fn validate_strict_rpki_name(
    name: &X509NameDer,
    role: &'static str,
) -> Result<(), ResourceCertificateProfileError> {
    let mut name_seq = DerReader::new(name.as_raw())
        .take_sequence()
        .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;

    let mut common_name_count = 0usize;
    let mut serial_number_count = 0usize;

    while !name_seq.is_empty() {
        let set_bytes = name_seq
            .take_tag(0x31)
            .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
        let mut rdn_set = DerReader::new(set_bytes);
        if rdn_set.is_empty() {
            return Err(ResourceCertificateProfileError::StrictName {
                role,
                detail: "RelativeDistinguishedName SET is empty".to_string(),
            });
        }

        while !rdn_set.is_empty() {
            let mut attr = rdn_set
                .take_sequence()
                .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
            let oid = attr
                .take_tag(0x06)
                .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
            let (value_tag, _value) = attr
                .take_any()
                .map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
            if !attr.is_empty() {
                return Err(ResourceCertificateProfileError::StrictName {
                    role,
                    detail: "AttributeTypeAndValue must be SEQUENCE of 2".to_string(),
                });
            }

            match oid {
                // 2.5.4.3 commonName
                &[0x55, 0x04, 0x03] => {
                    common_name_count += 1;
                    if value_tag != 0x13 {
                        return Err(ResourceCertificateProfileError::StrictName {
                            role,
                            detail: format!(
                                "commonName must be PrintableString, got tag 0x{value_tag:02X}"
                            ),
                        });
                    }
                }
                // 2.5.4.5 serialNumber
                &[0x55, 0x04, 0x05] => {
                    serial_number_count += 1;
                    if value_tag != 0x13 {
                        return Err(ResourceCertificateProfileError::StrictName {
                            role,
                            detail: format!(
                                "serialNumber must be PrintableString, got tag 0x{value_tag:02X}"
                            ),
                        });
                    }
                }
                _ => {}
            }
        }
    }

    if common_name_count != 1 {
        return Err(ResourceCertificateProfileError::StrictName {
            role,
            detail: format!("commonName must appear exactly once, got {common_name_count}"),
        });
    }
    if serial_number_count > 1 {
        return Err(ResourceCertificateProfileError::StrictName {
            role,
            detail: format!("serialNumber must appear at most once, got {serial_number_count}"),
        });
    }
    Ok(())
}

#[cfg(test)]
mod strict_name_tests {
    use super::*;

    fn name_with_attrs(attrs: &[(&[u8], u8, &[u8])]) -> X509NameDer {
        let mut rdns = Vec::new();
        for (oid, tag, value) in attrs {
            let mut attr = Vec::new();
            attr.extend(der_tlv(0x06, oid));
            attr.extend(der_tlv(*tag, value));
            let attr = der_tlv(0x30, &attr);
            let rdn = der_tlv(0x31, &attr);
            rdns.extend(rdn);
        }
        X509NameDer(der_tlv(0x30, &rdns))
    }

    fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
        let mut out = vec![tag];
        encode_len(value.len(), &mut out);
        out.extend_from_slice(value);
        out
    }

    fn encode_len(len: usize, out: &mut Vec<u8>) {
        if len < 0x80 {
            out.push(len as u8);
            return;
        }
        let mut bytes = Vec::new();
        let mut value = len;
        while value > 0 {
            bytes.push((value & 0xFF) as u8);
            value >>= 8;
        }
        bytes.reverse();
        out.push(0x80 | bytes.len() as u8);
        out.extend(bytes);
    }

    #[test]
    fn strict_name_accepts_printable_common_name_and_serial_number() {
        let name = name_with_attrs(&[
            (&[0x55, 0x04, 0x03], 0x13, b"CN1"),
            (&[0x55, 0x04, 0x05], 0x13, b"SN1"),
        ]);
        validate_strict_rpki_name(&name, "subject").expect("strict name");
    }

    #[test]
    fn strict_name_rejects_utf8_common_name() {
        let name = name_with_attrs(&[(&[0x55, 0x04, 0x03], 0x0C, b"CN1")]);
        let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
        assert!(err.to_string().contains("PrintableString"), "{err}");
    }

    #[test]
    fn strict_name_rejects_duplicate_common_name() {
        let name = name_with_attrs(&[
            (&[0x55, 0x04, 0x03], 0x13, b"CN1"),
            (&[0x55, 0x04, 0x03], 0x13, b"CN2"),
        ]);
        let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
        assert!(err.to_string().contains("exactly once"), "{err}");
    }

    #[test]
    fn profile_rejects_rfc8360_v2_policy_oid() {
        let extensions = RcExtensionsParsed {
            basic_constraints: vec![BasicConstraintsProfile {
                ca: true,
                critical: true,
                path_len_constraint: None,
            }],
            subject_key_identifier: Vec::new(),
            authority_key_identifier: Vec::new(),
            crl_distribution_points: Vec::new(),
            authority_info_access: Vec::new(),
            subject_info_access: Vec::new(),
            certificate_policies: vec![(
                vec![CertificatePoliciesProfile {
                    policy_oid: "1.3.6.1.5.5.7.14.3".to_string(),
                    qualifier_oids: Vec::new(),
                }],
                true,
            )],
            extension_oids: Vec::new(),
            ip_resources: Vec::new(),
            as_resources: Vec::new(),
        };

        let err = extensions
            .validate_profile(true)
            .expect_err("v2 policy OID must remain invalid");
        assert!(
            matches!(&err, ResourceCertificateProfileError::InvalidCertificatePolicy(oid) if oid == "1.3.6.1.5.5.7.14.3"),
            "{err}"
        );
    }
}

impl ResourceCertificateParsed {
    pub fn validate_profile(self) -> Result<ResourceCertificate, ResourceCertificateProfileError> {
        let version = match self.version {
            X509Version::V3 => 2u32,
            _ => return Err(ResourceCertificateProfileError::InvalidVersion),
        };

        self.validity_not_before
            .validate_encoding_rfc5280("notBefore")?;
        self.validity_not_after
            .validate_encoding_rfc5280("notAfter")?;

        if self.signature_algorithm != self.tbs_signature_algorithm {
            return Err(ResourceCertificateProfileError::SignatureAlgorithmMismatch);
        }
        if self.signature_algorithm.oid != OID_SHA256_WITH_RSA_ENCRYPTION {
            return Err(ResourceCertificateProfileError::UnsupportedSignatureAlgorithm);
        }
        if !self.signature_algorithm.params_absent_or_null() {
            return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters);
        }

        let is_self_signed = self.issuer_name == self.subject_name;
        let extensions = self.extensions.validate_profile(is_self_signed)?;
        let kind = if extensions.basic_constraints_ca {
            ResourceCertKind::Ca
        } else {
            ResourceCertKind::Ee
        };

        Ok(ResourceCertificate {
            raw_der: self.raw_der,
            tbs: RpkixTbsCertificate {
                version,
                serial_number: self.serial_number,
                signature_algorithm: self.signature_algorithm.oid,
                issuer_name: self.issuer_name,
                subject_name: self.subject_name,
                validity_not_before: self.validity_not_before.utc,
                validity_not_after: self.validity_not_after.utc,
                subject_public_key_info: self.subject_public_key_info,
                extensions,
            },
            kind,
        })
    }
}

impl RcExtensionsParsed {
    pub fn validate_profile(
        self,
        is_self_signed: bool,
    ) -> Result<RcExtensions, ResourceCertificateProfileError> {
        // NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
        // especially for large resource sets and URI lists.
        let RcExtensionsParsed {
            basic_constraints,
            subject_key_identifier,
            authority_key_identifier,
            crl_distribution_points,
            authority_info_access,
            subject_info_access,
            certificate_policies,
            extension_oids,
            ip_resources,
            as_resources,
        } = self;

        if basic_constraints.len() > 1 {
            return Err(ResourceCertificateProfileError::DuplicateExtension(
                "basicConstraints",
            ));
        }
        let basic_constraints = basic_constraints.into_iter().next();
        let basic_constraints_ca = basic_constraints.as_ref().is_some_and(|bc| bc.ca);

        let subject_key_identifier = match subject_key_identifier.len() {
            0 => None,
            1 => {
                let (ski, critical) = subject_key_identifier.into_iter().next().expect("len==1");
                if critical {
                    return Err(ResourceCertificateProfileError::SkiCriticality);
                }
                Some(ski)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "subjectKeyIdentifier",
                ));
            }
        };

        let authority_key_identifier = match authority_key_identifier.len() {
            0 => {
                if is_self_signed {
                    None
                } else {
                    return Err(ResourceCertificateProfileError::AkiMissing);
                }
            }
            1 => {
                let (aki, critical) = authority_key_identifier.into_iter().next().expect("len==1");
                if critical {
                    return Err(ResourceCertificateProfileError::AkiCriticality);
                }
                if aki.has_authority_cert_issuer {
                    return Err(ResourceCertificateProfileError::AkiAuthorityCertIssuerPresent);
                }
                if aki.has_authority_cert_serial {
                    return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
                }
                let keyid = aki.key_identifier;
                if is_self_signed {
                    if let (Some(keyid), Some(ski)) =
                        (keyid.as_ref(), subject_key_identifier.as_ref())
                    {
                        if keyid != ski {
                            return Err(ResourceCertificateProfileError::AkiSelfSignedNotEqualSki);
                        }
                    }
                } else if keyid.is_none() {
                    return Err(ResourceCertificateProfileError::AkiMissing);
                }
                keyid
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "authorityKeyIdentifier",
                ));
            }
        };

        let crl_distribution_points_uris = match crl_distribution_points.len() {
            0 => {
                if is_self_signed {
                    None
                } else {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
                }
            }
            1 => {
                let (crldp, critical) = crl_distribution_points.into_iter().next().expect("len==1");
                if critical {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
                }
                if is_self_signed {
                    return Err(
                        ResourceCertificateProfileError::CrlDistributionPointsSelfSignedMustOmit,
                    );
                }
                if crldp.distribution_points.len() != 1 {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
                }
                let dp = crldp
                    .distribution_points
                    .into_iter()
                    .next()
                    .expect("len==1");
                if dp.reasons_present {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
                }
                if dp.crl_issuer_present {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsHasCrlIssuer);
                }
                if !dp.distribution_point_present {
                    return Err(
                        ResourceCertificateProfileError::CrlDistributionPointsNoDistributionPoint,
                    );
                }
                if dp.name_relative_to_crl_issuer_present || !dp.full_name_present {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsInvalidName);
                }
                if dp.full_name_not_uri {
                    return Err(
                        ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri,
                    );
                }
                if !dp.full_name_uris.iter().any(|u| u.starts_with("rsync://")) {
                    return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
                }
                Some(dp.full_name_uris)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "cRLDistributionPoints",
                ));
            }
        };

        let ca_issuers_uris = match authority_info_access.len() {
            0 => {
                if is_self_signed {
                    None
                } else {
                    return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
                }
            }
            1 => {
                let (aia, critical) = authority_info_access.into_iter().next().expect("len==1");
                if critical {
                    return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
                }
                if is_self_signed {
                    return Err(
                        ResourceCertificateProfileError::AuthorityInfoAccessSelfSignedMustOmit,
                    );
                }
                if aia.ca_issuers_access_location_not_uri {
                    return Err(
                        ResourceCertificateProfileError::AuthorityInfoAccessCaIssuersNotUri,
                    );
                }
                if aia.ca_issuers_uris.is_empty() {
                    return Err(
                        ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers,
                    );
                }
                if !aia
                    .ca_issuers_uris
                    .iter()
                    .any(|u| u.starts_with("rsync://"))
                {
                    return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
                }
                Some(aia.ca_issuers_uris)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "authorityInfoAccess",
                ));
            }
        };

        let subject_info_access = match subject_info_access.len() {
            0 => None,
            1 => {
                let (sia, critical) = subject_info_access.into_iter().next().expect("len==1");
                if critical {
                    return Err(ResourceCertificateProfileError::SiaCriticality);
                }
                if sia.signed_object_access_location_not_uri {
                    return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri);
                }
                if !sia.signed_object_uris.is_empty()
                    && !sia
                        .signed_object_uris
                        .iter()
                        .any(|u| u.starts_with("rsync://"))
                {
                    return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync);
                }
                if sia.signed_object_uris.is_empty() {
                    Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
                        access_descriptions: sia.access_descriptions,
                    }))
                } else {
                    Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe {
                        signed_object_uris: sia.signed_object_uris,
                        access_descriptions: sia.access_descriptions,
                    }))
                }
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "subjectInfoAccess",
                ));
            }
        };

        let certificate_policies = match certificate_policies.len() {
            0 => None,
            1 => {
                let (policies, critical) = certificate_policies.into_iter().next().expect("len==1");
                if !critical {
                    return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
                }
                if policies.len() != 1 {
                    return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
                        "expected exactly one policy".into(),
                    ));
                }
                let policy = policies.into_iter().next().expect("len==1");
                if policy.policy_oid != OID_CP_IPADDR_ASNUMBER {
                    return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
                        policy.policy_oid,
                    ));
                }
                Some(policy)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "certificatePolicies",
                ));
            }
        };

        let ip_resources = match ip_resources.len() {
            0 => None,
            1 => {
                let (ip, critical) = ip_resources.into_iter().next().expect("len==1");
                if !critical {
                    return Err(ResourceCertificateProfileError::IpResourcesCriticality);
                }
                Some(ip)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "ipAddrBlocks",
                ));
            }
        };

        let as_resources = match as_resources.len() {
            0 => None,
            1 => {
                let (asn, critical) = as_resources.into_iter().next().expect("len==1");
                if !critical {
                    return Err(ResourceCertificateProfileError::AsResourcesCriticality);
                }
                Some(asn)
            }
            _ => {
                return Err(ResourceCertificateProfileError::DuplicateExtension(
                    "autonomousSysIds",
                ));
            }
        };

        Ok(RcExtensions {
            basic_constraints_ca,
            basic_constraints,
            subject_key_identifier,
            authority_key_identifier,
            crl_distribution_points_uris,
            ca_issuers_uris,
            subject_info_access,
            certificate_policies_oid: certificate_policies
                .as_ref()
                .map(|_| OID_CP_IPADDR_ASNUMBER.to_string()),
            certificate_policies,
            extension_oids,
            ip_resources,
            as_resources,
        })
    }
}

fn algorithm_identifier_value(
    ai: &x509_parser::x509::AlgorithmIdentifier<'_>,
) -> AlgorithmIdentifierValue {
    let parameters = ai.parameters.as_ref().map(|p| AlgorithmParametersValue {
        class: p.class(),
        tag: p.tag(),
        data: p.as_bytes().to_vec(),
    });
    // NOTE(perf): Avoid `to_id_string()` allocations for the algorithms we expect
    // in RPKI resource certificates. Fall back to `to_id_string()` for unexpected
    // algorithms (mostly error paths).
    let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
        OID_SHA256_WITH_RSA_ENCRYPTION.to_string()
    } else {
        ai.algorithm.to_id_string()
    };
    AlgorithmIdentifierValue { oid, parameters }
}

fn parse_extensions_parse(
    exts: &[X509Extension<'_>],
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
    let mut basic_constraints: Vec<BasicConstraintsProfile> = Vec::new();
    let mut ski: Vec<(Vec<u8>, bool)> = Vec::new();
    let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
    let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
    let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
    let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new();
    let mut cert_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)> = Vec::new();
    let mut extension_oids: Vec<String> = Vec::with_capacity(exts.len());

    let mut ip_resources: Vec<(IpResourceSet, bool)> = Vec::new();
    let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();

    for ext in exts {
        let oid = ext.oid.as_bytes();
        extension_oids.push(ext.oid.to_id_string());
        if oid == OID_BASIC_CONSTRAINTS_RAW {
            let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "basicConstraints parse failed".into(),
                ));
            };
            basic_constraints.push(BasicConstraintsProfile {
                ca: bc.ca,
                critical: ext.critical,
                path_len_constraint: bc.path_len_constraint,
            });
        } else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
            let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "subjectKeyIdentifier parse failed".into(),
                ));
            };
            ski.push((s.0.to_vec(), ext.critical));
        } else if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
            let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "authorityKeyIdentifier parse failed".into(),
                ));
            };
            aki.push((
                AuthorityKeyIdentifierParsed {
                    key_identifier: a.key_identifier.as_ref().map(|k| k.0.to_vec()),
                    has_authority_cert_issuer: a.authority_cert_issuer.is_some(),
                    has_authority_cert_serial: a.authority_cert_serial.is_some(),
                },
                ext.critical,
            ));
        } else if oid == OID_CRL_DISTRIBUTION_POINTS_RAW {
            let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "cRLDistributionPoints parse failed".into(),
                ));
            };
            crldp.push((parse_crldp_parse(p)?, ext.critical));
        } else if oid == OID_AUTHORITY_INFO_ACCESS_RAW {
            let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "authorityInfoAccess parse failed".into(),
                ));
            };
            aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
        } else if oid == OID_SUBJECT_INFO_ACCESS_RAW {
            let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "subjectInfoAccess parse failed".into(),
                ));
            };
            sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical));
        } else if oid == OID_CERTIFICATE_POLICIES_RAW {
            let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
                return Err(ResourceCertificateParseError::Parse(
                    "certificatePolicies parse failed".into(),
                ));
            };
            let mut policies: Vec<CertificatePoliciesProfile> = Vec::with_capacity(cp.len());
            for p in cp.iter() {
                let b = p.policy_id.as_bytes();
                let policy_oid = if b == OID_CP_IPADDR_ASNUMBER_RAW {
                    OID_CP_IPADDR_ASNUMBER.to_string()
                } else {
                    p.policy_id.to_id_string()
                };
                let qualifier_oids = p
                    .policy_qualifiers
                    .as_ref()
                    .map(|qualifiers| {
                        qualifiers
                            .iter()
                            .map(|qualifier| qualifier.policy_qualifier_id.to_id_string())
                            .collect()
                    })
                    .unwrap_or_default();
                policies.push(CertificatePoliciesProfile {
                    policy_oid,
                    qualifier_oids,
                });
            }
            cert_policies.push((policies, ext.critical));
        } else if oid == OID_IP_ADDR_BLOCKS_RAW {
            let parsed = IpResourceSet::decode_extn_value(ext.value)
                .map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
            ip_resources.push((parsed, ext.critical));
        } else if oid == OID_AUTONOMOUS_SYS_IDS_RAW {
            let parsed = AsResourceSet::decode_extn_value(ext.value)
                .map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?;
            as_resources.push((parsed, ext.critical));
        }
    }

    Ok(RcExtensionsParsed {
        basic_constraints,
        subject_key_identifier: ski,
        authority_key_identifier: aki,
        crl_distribution_points: crldp,
        authority_info_access: aia,
        subject_info_access: sia,
        certificate_policies: cert_policies,
        extension_oids,
        ip_resources,
        as_resources,
    })
}

fn parse_aia_parse(
    access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<AuthorityInfoAccessParsed, ResourceCertificateParseError> {
    let mut ca_issuers_uris: Vec<String> = Vec::new();
    let mut ca_issuers_access_location_not_uri = false;

    for ad in access {
        if ad.access_method.as_bytes() != OID_AD_CA_ISSUERS_RAW {
            continue;
        }
        let uri = match &ad.access_location {
            x509_parser::extensions::GeneralName::URI(u) => u,
            _ => {
                ca_issuers_access_location_not_uri = true;
                continue;
            }
        };
        ca_issuers_uris.push(uri.to_string());
    }

    Ok(AuthorityInfoAccessParsed {
        ca_issuers_uris,
        ca_issuers_access_location_not_uri,
    })
}

fn parse_crldp_parse(
    crldp: &x509_parser::extensions::CRLDistributionPoints<'_>,
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
    let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
    for p in crldp.iter() {
        let mut full_name_uris: Vec<String> = Vec::new();
        let mut full_name_not_uri = false;
        let mut full_name_present = false;
        let mut name_relative_to_crl_issuer_present = false;
        let mut distribution_point_present = false;

        if let Some(dp) = &p.distribution_point {
            distribution_point_present = true;
            match dp {
                x509_parser::extensions::DistributionPointName::FullName(names) => {
                    full_name_present = true;
                    for n in names {
                        match n {
                            x509_parser::extensions::GeneralName::URI(u) => {
                                full_name_uris.push(u.to_string());
                            }
                            _ => {
                                full_name_not_uri = true;
                            }
                        }
                    }
                }
                x509_parser::extensions::DistributionPointName::NameRelativeToCRLIssuer(_) => {
                    name_relative_to_crl_issuer_present = true;
                }
            }
        }

        out.push(CrlDistributionPointParsed {
            distribution_point_present,
            reasons_present: p.reasons.is_some(),
            crl_issuer_present: p.crl_issuer.is_some(),
            name_relative_to_crl_issuer_present,
            full_name_uris,
            full_name_not_uri,
            full_name_present,
        });
    }
    Ok(CrlDistributionPointsParsed {
        distribution_points: out,
    })
}

fn parse_sia_parse(
    access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {
    let mut all = Vec::with_capacity(access.len());
    let mut signed_object_uris: Vec<String> = Vec::new();
    let mut signed_object_access_location_not_uri = false;

    for ad in access {
        let access_method_oid = if ad.access_method.as_bytes() == OID_AD_CA_REPOSITORY_RAW {
            OID_AD_CA_REPOSITORY.to_string()
        } else if ad.access_method.as_bytes() == OID_AD_RPKI_MANIFEST_RAW {
            OID_AD_RPKI_MANIFEST.to_string()
        } else if ad.access_method.as_bytes() == OID_AD_RPKI_NOTIFY_RAW {
            OID_AD_RPKI_NOTIFY.to_string()
        } else if ad.access_method.as_bytes() == OID_AD_SIGNED_OBJECT_RAW {
            OID_AD_SIGNED_OBJECT.to_string()
        } else {
            ad.access_method.to_id_string()
        };
        let is_signed_object = access_method_oid == OID_AD_SIGNED_OBJECT;
        let uri = match &ad.access_location {
            x509_parser::extensions::GeneralName::URI(u) => u,
            _ => {
                if is_signed_object {
                    signed_object_access_location_not_uri = true;
                }
                continue;
            }
        };
        if is_signed_object {
            signed_object_uris.push(uri.to_string());
        }
        all.push(AccessDescription {
            access_method_oid,
            access_location: uri.to_string(),
        });
    }

    Ok(SubjectInfoAccessParsed {
        access_descriptions: all,
        signed_object_uris,
        signed_object_access_location_not_uri,
    })
}

fn parse_ip_addr_blocks(ext_value: &[u8]) -> Result<IpResourceSet, ()> {
    let (rem, obj) = parse_der(ext_value).map_err(|_| ())?;
    if !rem.is_empty() {
        return Err(());
    }
    let seq = obj.as_sequence().map_err(|_| ())?;
    let mut families = Vec::with_capacity(seq.len());
    for fam in seq {
        let fam_seq = fam.as_sequence().map_err(|_| ())?;
        if fam_seq.len() != 2 {
            return Err(());
        }
        let af_bytes = fam_seq[0].as_slice().map_err(|_| ())?;
        if af_bytes.len() != 2 {
            return Err(());
        }
        let afi = match af_bytes {
            [0x00, 0x01] => Afi::Ipv4,
            [0x00, 0x02] => Afi::Ipv6,
            _ => return Err(()),
        };

        let choice = match &fam_seq[1].content {
            BerObjectContent::Null => IpAddressChoice::Inherit,
            BerObjectContent::Sequence(_) => {
                let items_seq = fam_seq[1].as_sequence().map_err(|_| ())?;
                let mut items = Vec::with_capacity(items_seq.len());
                for item in items_seq {
                    items.push(parse_ip_address_or_range(afi, item)?);
                }
                IpAddressChoice::AddressesOrRanges(items)
            }
            _ => return Err(()),
        };
        families.push(IpAddressFamily { afi, choice });
    }
    Ok(IpResourceSet { families })
}

fn parse_ip_address_or_range(afi: Afi, obj: &DerObject<'_>) -> Result<IpAddressOrRange, ()> {
    match &obj.content {
        BerObjectContent::BitString(_, _) => {
            Ok(IpAddressOrRange::Prefix(parse_ip_prefix(afi, obj)?))
        }
        BerObjectContent::Sequence(_) => {
            let seq = obj.as_sequence().map_err(|_| ())?;
            if seq.len() != 2 {
                return Err(());
            }
            let min = parse_ip_address_bound(afi, &seq[0], false)?;
            let max = parse_ip_address_bound(afi, &seq[1], true)?;
            Ok(IpAddressOrRange::Range(IpAddressRange { min, max }))
        }
        _ => Err(()),
    }
}

fn parse_ip_prefix(afi: Afi, obj: &DerObject<'_>) -> Result<IpPrefix, ()> {
    let (unused_bits, bytes) = match &obj.content {
        BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
        _ => return Err(()),
    };
    if unused_bits > 7 {
        return Err(());
    }
    if !bytes.is_empty() && unused_bits != 0 {
        let mask = (1u8 << unused_bits) - 1;
        if (bytes[bytes.len() - 1] & mask) != 0 {
            return Err(());
        }
    } else if bytes.is_empty() && unused_bits != 0 {
        return Err(());
    }
    let prefix_len = (bytes.len() * 8)
        .checked_sub(unused_bits as usize)
        .ok_or(())? as u16;
    if prefix_len > afi.ub() {
        return Err(());
    }
    let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes);
    Ok(IpPrefix {
        afi,
        prefix_len,
        addr,
    })
}

/// Parse an RFC 3779 `IPAddress` BIT STRING into an address-like byte array.
///
/// When used as an `IPAddressRange` endpoint, RFC 3779 allows endpoints to be encoded with
/// fewer than `ub` bits. In that case, the missing bits are interpreted as 0s for the lower
/// bound and 1s for the upper bound. This is essential to correctly interpret ranges that
/// are expressed on non-octet boundaries.
fn parse_ip_address_bound(
    afi: Afi,
    obj: &DerObject<'_>,
    fill_remaining_ones: bool,
) -> Result<Vec<u8>, ()> {
    let (unused_bits, bytes) = match &obj.content {
        BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
        _ => return Err(()),
    };
    if unused_bits > 7 {
        return Err(());
    }
    if !bytes.is_empty() && unused_bits != 0 {
        let mask = (1u8 << unused_bits) - 1;
        if (bytes[bytes.len() - 1] & mask) != 0 {
            return Err(());
        }
    } else if bytes.is_empty() && unused_bits != 0 {
        return Err(());
    }

    let bit_len: u16 = (bytes.len() * 8)
        .checked_sub(unused_bits as usize)
        .ok_or(())?
        .try_into()
        .map_err(|_| ())?;
    if bit_len > afi.ub() {
        return Err(());
    }

    let mut out = vec![0u8; afi.octets_len()];
    let copy_len = bytes.len().min(out.len());
    out[..copy_len].copy_from_slice(&bytes[..copy_len]);

    if fill_remaining_ones {
        if bit_len == 0 {
            for b in &mut out {
                *b = 0xFF;
            }
            return Ok(out);
        }

        let last_bit = (bit_len - 1) as usize;
        let last_byte = last_bit / 8;
        let rem = (bit_len % 8) as u8;

        if rem != 0 && last_byte < out.len() {
            // Set the (8-rem) trailing bits in the last byte to 1.
            let mask: u8 = (1u8 << (8 - rem)) - 1;
            out[last_byte] |= mask;
        }
        for b in out.iter_mut().skip(last_byte + 1) {
            *b = 0xFF;
        }
    }

    Ok(out)
}

fn parse_as_identifiers(ext_value: &[u8]) -> Result<AsResourceSet, ()> {
    let (rem, obj) = parse_der(ext_value).map_err(|_| ())?;
    if !rem.is_empty() {
        return Err(());
    }
    let seq = obj.as_sequence().map_err(|_| ())?;
    let mut asnum: Option<AsIdentifierChoice> = None;
    let mut rdi: Option<AsIdentifierChoice> = None;
    for item in seq {
        if item.class() != Class::ContextSpecific {
            return Err(());
        }
        match item.tag() {
            Tag(0) => {
                if asnum.is_some() {
                    return Err(());
                }
                let inner = parse_explicit_inner(item)?;
                asnum = Some(parse_as_identifier_choice(&inner)?);
            }
            Tag(1) => {
                if rdi.is_some() {
                    return Err(());
                }
                let inner = parse_explicit_inner(item)?;
                rdi = Some(parse_as_identifier_choice(&inner)?);
            }
            _ => return Err(()),
        }
    }
    Ok(AsResourceSet { asnum, rdi })
}

fn parse_explicit_inner<'a>(obj: &'a DerObject<'a>) -> Result<DerObject<'a>, ()> {
    let inner_der = obj.as_slice().map_err(|_| ())?;
    let (rem, inner) = parse_der(inner_der).map_err(|_| ())?;
    if !rem.is_empty() {
        return Err(());
    }
    Ok(inner)
}

fn parse_as_identifier_choice(obj: &DerObject<'_>) -> Result<AsIdentifierChoice, ()> {
    match &obj.content {
        BerObjectContent::Null => Ok(AsIdentifierChoice::Inherit),
        BerObjectContent::Sequence(_) => {
            let seq = obj.as_sequence().map_err(|_| ())?;
            let mut items = Vec::with_capacity(seq.len());
            for item in seq {
                items.push(parse_as_id_or_range(item)?);
            }
            Ok(AsIdentifierChoice::AsIdsOrRanges(items))
        }
        _ => Err(()),
    }
}

fn parse_as_id_or_range(obj: &DerObject<'_>) -> Result<AsIdOrRange, ()> {
    match &obj.content {
        BerObjectContent::Integer(_) => {
            let v = obj.as_u64().map_err(|_| ())?;
            if v > u32::MAX as u64 {
                return Err(());
            }
            Ok(AsIdOrRange::Id(v as u32))
        }
        BerObjectContent::Sequence(_) => {
            let seq = obj.as_sequence().map_err(|_| ())?;
            if seq.len() != 2 {
                return Err(());
            }
            let min = seq[0].as_u64().map_err(|_| ())?;
            let max = seq[1].as_u64().map_err(|_| ())?;
            if min > u32::MAX as u64 || max > u32::MAX as u64 || min > max {
                return Err(());
            }
            Ok(AsIdOrRange::Range {
                min: min as u32,
                max: max as u32,
            })
        }
        _ => Err(()),
    }
}

fn canonicalize_prefix_addr(afi: Afi, prefix_len: u16, bytes: &[u8]) -> Vec<u8> {
    let full_len = afi.octets_len();
    let mut addr = vec![0u8; full_len];
    let copy_len = bytes.len().min(full_len);
    addr[..copy_len].copy_from_slice(&bytes[..copy_len]);

    if prefix_len == 0 {
        return addr;
    }

    let last_prefix_bit = (prefix_len - 1) as usize;
    let last_prefix_byte = last_prefix_bit / 8;
    let rem = (prefix_len % 8) as u8;
    if rem != 0 && last_prefix_byte < addr.len() {
        let mask: u8 = 0xFF << (8 - rem);
        addr[last_prefix_byte] &= mask;
    }
    addr
}

fn prefix_covers(resource: &IpPrefix, subject: &IpPrefix) -> bool {
    if resource.afi != subject.afi {
        return false;
    }
    if resource.prefix_len > subject.prefix_len {
        return false;
    }
    let n = resource.prefix_len as usize;
    let whole = n / 8;
    let rem = (n % 8) as u8;
    if resource.addr.len() != subject.addr.len() {
        return false;
    }
    if resource.addr[..whole] != subject.addr[..whole] {
        return false;
    }
    if rem == 0 {
        return true;
    }
    let mask = 0xFFu8 << (8 - rem);
    (resource.addr[whole] & mask) == (subject.addr[whole] & mask)
}

fn prefix_range(afi: Afi, p: &IpPrefix) -> (u128, u128) {
    let mut base_bytes = [0u8; 16];
    match afi {
        Afi::Ipv4 => {
            base_bytes[12..].copy_from_slice(&p.addr[..4]);
        }
        Afi::Ipv6 => {
            base_bytes.copy_from_slice(&p.addr[..16]);
        }
    }
    let base = u128::from_be_bytes(base_bytes);
    let host_bits = (afi.ub() - p.prefix_len) as u32;
    if host_bits == 0 {
        return (base, base);
    }
    let mask = (1u128 << host_bits) - 1;
    (base, base | mask)
}

fn range_covers_prefix(afi: Afi, r: &IpAddressRange, p: &IpPrefix) -> bool {
    let (p_min, p_max) = prefix_range(afi, p);
    let r_min = bytes_to_u128(afi, &r.min);
    let r_max = bytes_to_u128(afi, &r.max);
    r_min <= p_min && p_max <= r_max
}

fn bytes_to_u128(afi: Afi, bytes: &[u8]) -> u128 {
    let mut out = [0u8; 16];
    match afi {
        Afi::Ipv4 => {
            let copy_len = bytes.len().min(4);
            out[12..12 + copy_len].copy_from_slice(&bytes[..copy_len]);
        }
        Afi::Ipv6 => {
            let copy_len = bytes.len().min(16);
            out[..copy_len].copy_from_slice(&bytes[..copy_len]);
        }
    }
    u128::from_be_bytes(out)
}
