use std::process::Command;

use rpki::data_model::rc::ResourceCertificate;
use rpki::validation::ca_path::{CaPathError, validate_subordinate_ca_cert};

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

fn skip_heavy_crypto_test() -> bool {
    std::env::var_os("RPKI_SKIP_HEAVY_CRYPTO_TESTS").is_some()
}

struct Generated {
    issuer_ca_der: Vec<u8>,
    child_ca_der: Vec<u8>,
    issuer_crl_der: Vec<u8>,
}

fn write(path: &std::path::Path, s: &str) {
    std::fs::write(path, s.as_bytes()).expect("write file");
}

fn run(cmd: &mut Command) {
    let out = cmd.output().expect("run command");
    if !out.status.success() {
        panic!(
            "command failed: {:?}\nstdout={}\nstderr={}",
            cmd,
            String::from_utf8_lossy(&out.stdout),
            String::from_utf8_lossy(&out.stderr)
        );
    }
}

fn generate_chain_and_crl(child_ext: &str, revoke_child: bool) -> Generated {
    assert!(openssl_available(), "openssl is required for this test");

    let td = tempfile::tempdir().expect("tempdir");
    let dir = td.path();

    // Minimal CA database layout required by `openssl ca`.
    std::fs::create_dir_all(dir.join("newcerts")).expect("newcerts");
    std::fs::write(dir.join("index.txt"), b"").expect("index");
    std::fs::write(dir.join("serial"), b"1000\n").expect("serial");
    std::fs::write(dir.join("crlnumber"), b"1000\n").expect("crlnumber");

    let cnf = format!(
        r#"
[ ca ]
default_ca = CA_default

[ CA_default ]
dir = {dir}
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/issuer.pem
private_key = $dir/issuer.key
serial = $dir/serial
crlnumber = $dir/crlnumber
default_md = sha256
default_days = 365
default_crl_days = 1
policy = policy_any
x509_extensions = v3_issuer_ca
crl_extensions = crl_ext
unique_subject = no
copy_extensions = none

[ policy_any ]
commonName = supplied

[ req ]
prompt = no
distinguished_name = dn

[ dn ]
CN = Test Issuer CA

[ v3_issuer_ca ]
basicConstraints = critical,CA:true
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
certificatePolicies = critical, 1.3.6.1.5.5.7.14.2
subjectInfoAccess = caRepository;URI:rsync://example.test/repo/issuer/, rpkiManifest;URI:rsync://example.test/repo/issuer/issuer.mft, rpkiNotify;URI:https://example.test/notification.xml
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/8
sbgp-autonomousSysNum = critical, AS:64496-64511

[ v3_child_ca ]
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
crlDistributionPoints = URI:rsync://example.test/repo/issuer/issuer.crl
authorityInfoAccess = caIssuers;URI:rsync://example.test/repo/issuer/issuer.cer
certificatePolicies = critical, 1.3.6.1.5.5.7.14.2
subjectInfoAccess = caRepository;URI:rsync://example.test/repo/child/, rpkiManifest;URI:rsync://example.test/repo/child/child.mft, rpkiNotify;URI:https://example.test/notification.xml
{child_ext}

[ crl_ext ]
authorityKeyIdentifier = keyid:always
"#,
        dir = dir.display(),
        child_ext = child_ext
    );
    write(&dir.join("openssl.cnf"), &cnf);

    // Issuer CA key + self-signed CA cert (DER later).
    run(Command::new("openssl")
        .arg("genrsa")
        .arg("-out")
        .arg(dir.join("issuer.key"))
        .arg("2048"));
    run(Command::new("openssl")
        .arg("req")
        .arg("-new")
        .arg("-x509")
        .arg("-sha256")
        .arg("-days")
        .arg("365")
        .arg("-key")
        .arg(dir.join("issuer.key"))
        .arg("-out")
        .arg(dir.join("issuer.pem"))
        .arg("-config")
        .arg(dir.join("openssl.cnf"))
        .arg("-extensions")
        .arg("v3_issuer_ca"));

    // Child CA key + CSR.
    run(Command::new("openssl")
        .arg("genrsa")
        .arg("-out")
        .arg(dir.join("child.key"))
        .arg("2048"));
    run(Command::new("openssl")
        .arg("req")
        .arg("-new")
        .arg("-key")
        .arg(dir.join("child.key"))
        .arg("-subj")
        .arg("/CN=Child CA")
        .arg("-out")
        .arg(dir.join("child.csr"))
        .arg("-config")
        .arg(dir.join("openssl.cnf")));

    // Issue child CA cert using openssl ca (so it appears in the CA database for CRL).
    run(Command::new("openssl")
        .arg("ca")
        .arg("-batch")
        .arg("-config")
        .arg(dir.join("openssl.cnf"))
        .arg("-extensions")
        .arg("v3_child_ca")
        .arg("-in")
        .arg(dir.join("child.csr"))
        .arg("-out")
        .arg(dir.join("child.pem"))
        .arg("-notext"));

    if revoke_child {
        run(Command::new("openssl")
            .arg("ca")
            .arg("-config")
            .arg(dir.join("openssl.cnf"))
            .arg("-revoke")
            .arg(dir.join("child.pem")));
    }

    // Generate CRL.
    run(Command::new("openssl")
        .arg("ca")
        .arg("-gencrl")
        .arg("-config")
        .arg(dir.join("openssl.cnf"))
        .arg("-out")
        .arg(dir.join("issuer.crl.pem")));

    // Convert to DER.
    run(Command::new("openssl")
        .arg("x509")
        .arg("-in")
        .arg(dir.join("issuer.pem"))
        .arg("-outform")
        .arg("DER")
        .arg("-out")
        .arg(dir.join("issuer.cer")));
    run(Command::new("openssl")
        .arg("x509")
        .arg("-in")
        .arg(dir.join("child.pem"))
        .arg("-outform")
        .arg("DER")
        .arg("-out")
        .arg(dir.join("child.cer")));
    run(Command::new("openssl")
        .arg("crl")
        .arg("-in")
        .arg(dir.join("issuer.crl.pem"))
        .arg("-outform")
        .arg("DER")
        .arg("-out")
        .arg(dir.join("issuer.crl")));

    Generated {
        issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
        child_ca_der: std::fs::read(dir.join("child.cer")).expect("read child der"),
        issuer_crl_der: std::fs::read(dir.join("issuer.crl")).expect("read crl der"),
    }
}

#[test]
fn validate_subordinate_ca_succeeds_for_valid_child_and_subset_resources() {
    if skip_heavy_crypto_test() {
        return;
    }
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\nsbgp-autonomousSysNum = critical, AS:64496\n",
        false,
    );

    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");

    let now = time::OffsetDateTime::now_utc();
    let validated = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .expect("validate subordinate");

    assert!(validated.effective_ip_resources.is_some());
    assert!(validated.effective_as_resources.is_some());
}

#[test]
fn validate_subordinate_ca_rejects_wrong_key_usage_bits() {
    if skip_heavy_crypto_test() {
        return;
    }
    let generated = generate_chain_and_crl(
        "keyUsage = critical, digitalSignature\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );

    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::KeyUsageInvalidBits));
}

#[test]
fn validate_subordinate_ca_rejects_out_of_scope_resources() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:11.0.0.0/8\n",
        false,
    );

    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ResourcesNotSubset));
}

#[test]
fn validate_subordinate_ca_rejects_revoked_child() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        true,
    );

    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ChildRevoked));
}

#[test]
fn validate_subordinate_ca_rejects_missing_key_usage_extension() {
    let generated = generate_chain_and_crl(
        "sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\nsbgp-autonomousSysNum = critical, AS:64496\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::KeyUsageMissing));
}

#[test]
fn validate_subordinate_ca_rejects_non_critical_key_usage() {
    let generated = generate_chain_and_crl(
        "keyUsage = keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::KeyUsageNotCritical));
}

#[test]
fn validate_subordinate_ca_rejects_when_child_has_no_resources() {
    let generated = generate_chain_and_crl("keyUsage = critical, keyCertSign, cRLSign\n", false);
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ResourcesMissing));
}

#[test]
fn validate_subordinate_ca_rejects_when_cert_not_valid_at_validation_time() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let validation_time = time::OffsetDateTime::now_utc() + time::Duration::days(400);
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        validation_time,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::CertificateNotValidAtTime));
}

#[test]
fn validate_subordinate_ca_rejects_when_crl_not_valid_at_validation_time() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let validation_time = time::OffsetDateTime::now_utc() + time::Duration::days(2);
    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        validation_time,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::CrlNotValidAtTime));
}

#[test]
fn validate_subordinate_ca_rejects_tampered_child_signature() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();

    let mut tampered = generated.child_ca_der.clone();
    if let Some(last) = tampered.last_mut() {
        *last ^= 0x01;
    }

    let err = validate_subordinate_ca_cert(
        &tampered,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ChildSignatureInvalid(_)));
}

#[test]
fn validate_subordinate_ca_rejects_tampered_crl_signature() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let now = time::OffsetDateTime::now_utc();

    let mut tampered = generated.issuer_crl_der.clone();
    if let Some(last) = tampered.last_mut() {
        *last ^= 0x01;
    }

    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &generated.issuer_ca_der,
        &tampered,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::CrlVerify(_)));
}

#[test]
fn validate_subordinate_ca_rejects_non_ca_child_and_non_ca_issuer() {
    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let ee_child_der =
        std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
            .expect("read roa fixture");
    let roa = rpki::data_model::roa::RoaObject::decode_der(&ee_child_der).expect("decode roa");
    let ee_der = roa.signed_object.signed_data.certificates[0]
        .raw_der
        .clone();

    let err = validate_subordinate_ca_cert(
        &ee_der,
        &generated.issuer_ca_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ChildNotCa), "{err}");

    let err = validate_subordinate_ca_cert(
        &generated.child_ca_der,
        &ee_der,
        &generated.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::IssuerNotCa), "{err}");
}

#[test]
fn validate_subordinate_ca_rejects_mismatched_issuer_subject_and_missing_resources() {
    let with_resources = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
sbgp-autonomousSysNum = critical, AS:64496
",
        false,
    );
    let issuer =
        ResourceCertificate::decode_der(&with_resources.issuer_ca_der).expect("decode issuer");
    let wrong_issuer_der = std::fs::read(
        "tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
    )
    .expect("read wrong issuer fixture");
    let wrong_issuer =
        ResourceCertificate::decode_der(&wrong_issuer_der).expect("decode wrong issuer");

    let err = validate_subordinate_ca_cert(
        &with_resources.child_ca_der,
        &wrong_issuer_der,
        &with_resources.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        wrong_issuer.tbs.extensions.ip_resources.as_ref(),
        wrong_issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(
        matches!(err, CaPathError::IssuerSubjectMismatch { .. }),
        "{err}"
    );

    let no_resources = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
",
        false,
    );
    let err = validate_subordinate_ca_cert(
        &no_resources.child_ca_der,
        &no_resources.issuer_ca_der,
        &no_resources.issuer_crl_der,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ResourcesMissing), "{err}");
}

#[test]
fn validate_subordinate_ca_with_prevalidated_issuer_covers_success_and_error_paths() {
    use rpki::data_model::common::BigUnsigned;
    use rpki::data_model::crl::RpkixCrl;
    use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
    use std::collections::HashSet;
    use x509_parser::prelude::FromDer;
    use x509_parser::x509::SubjectPublicKeyInfo;

    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
sbgp-autonomousSysNum = critical, AS:64496
",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
    let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
    let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
        .expect("parse issuer spki");
    assert!(rem.is_empty());
    let now = time::OffsetDateTime::now_utc();

    let validated = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        child.clone(),
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .expect("validate subordinate with prevalidated issuer");
    assert!(validated.effective_ip_resources.is_some());
    assert!(validated.effective_as_resources.is_some());

    let mut revoked = HashSet::new();
    revoked.insert(BigUnsigned::from_biguint(&child.tbs.serial_number).bytes_be);
    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        child,
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &revoked,
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        now,
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ChildRevoked), "{err}");
}

#[test]
fn validate_subordinate_ca_with_prevalidated_issuer_rejects_non_ca_inputs_and_invalid_times() {
    use rpki::data_model::crl::RpkixCrl;
    use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
    use std::collections::HashSet;
    use x509_parser::prelude::FromDer;
    use x509_parser::x509::SubjectPublicKeyInfo;

    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
sbgp-autonomousSysNum = critical, AS:64496
",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
    let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
    let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
        .expect("parse issuer spki");
    assert!(rem.is_empty());

    let roa_der =
        std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa")
            .expect("read roa fixture");
    let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_der).expect("decode roa");
    let ee_rc =
        ResourceCertificate::decode_der(&roa.signed_object.signed_data.certificates[0].raw_der)
            .expect("decode ee rc");
    let (rem, ee_spki) =
        SubjectPublicKeyInfo::from_der(&ee_rc.tbs.subject_public_key_info).expect("parse ee spki");
    assert!(rem.is_empty());

    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        child.clone(),
        &ee_rc,
        &ee_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::IssuerNotCa), "{err}");

    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        ee_rc,
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ChildNotCa), "{err}");

    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        child.clone(),
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        child.tbs.validity_not_before - time::Duration::seconds(1),
    )
    .unwrap_err();
    assert!(
        matches!(err, CaPathError::CertificateNotValidAtTime),
        "{err}"
    );

    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        child,
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        issuer_crl.next_update.utc + time::Duration::seconds(1),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::CrlNotValidAtTime), "{err}");
}

#[test]
fn validate_subordinate_ca_with_prevalidated_issuer_rejects_mismatch_and_missing_resources() {
    use rpki::data_model::crl::RpkixCrl;
    use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer;
    use std::collections::HashSet;
    use x509_parser::prelude::FromDer;
    use x509_parser::x509::SubjectPublicKeyInfo;

    let generated = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16
sbgp-autonomousSysNum = critical, AS:64496
",
        false,
    );
    let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer");
    let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child");
    let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl");
    let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info)
        .expect("parse issuer spki");
    assert!(rem.is_empty());

    let mut mismatched_child = child.clone();
    mismatched_child.tbs.issuer_name = mismatched_child.tbs.subject_name.clone();

    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &generated.child_ca_der,
        mismatched_child,
        &issuer,
        &issuer_spki,
        &issuer_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        issuer.tbs.extensions.ip_resources.as_ref(),
        issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(
        matches!(err, CaPathError::IssuerSubjectMismatch { .. }),
        "{err}"
    );

    let no_resources = generate_chain_and_crl(
        "keyUsage = critical, keyCertSign, cRLSign
",
        false,
    );
    let no_resources_child = ResourceCertificate::decode_der(&no_resources.child_ca_der)
        .expect("decode no resources child");
    let no_resources_issuer = ResourceCertificate::decode_der(&no_resources.issuer_ca_der)
        .expect("decode no resources issuer");
    let no_resources_crl =
        RpkixCrl::decode_der(&no_resources.issuer_crl_der).expect("decode no resources crl");
    let (rem, no_resources_spki) =
        SubjectPublicKeyInfo::from_der(&no_resources_issuer.tbs.subject_public_key_info)
            .expect("parse no resources issuer spki");
    assert!(rem.is_empty());
    let err = validate_subordinate_ca_cert_with_prevalidated_issuer(
        &no_resources.child_ca_der,
        no_resources_child,
        &no_resources_issuer,
        &no_resources_spki,
        &no_resources_crl,
        &HashSet::new(),
        Some("rsync://example.test/repo/issuer/issuer.cer"),
        "rsync://example.test/repo/issuer/issuer.crl",
        no_resources_issuer.tbs.extensions.ip_resources.as_ref(),
        no_resources_issuer.tbs.extensions.as_resources.as_ref(),
        time::OffsetDateTime::now_utc(),
    )
    .unwrap_err();
    assert!(matches!(err, CaPathError::ResourcesMissing), "{err}");
}
