use rpki::parallel::config::ParallelPhase2Config;
use rpki::policy::{Policy, SignedObjectFailurePolicy};
use rpki::storage::{PackFile, PackTime};
use rpki::validation::objects::{
    ParallelRoaWorkerPool, process_publication_point_for_issuer_parallel_roa_with_pool,
    process_publication_point_snapshot_for_issuer,
    process_publication_point_snapshot_for_issuer_parallel_roa,
};
use rpki::validation::publication_point::PublicationPointSnapshot;

fn fixture_bytes(path: &str) -> Vec<u8> {
    std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
        .unwrap_or_else(|e| panic!("read fixture {path}: {e}"))
}

fn dummy_snapshot(manifest_bytes: Vec<u8>, files: Vec<PackFile>) -> PublicationPointSnapshot {
    let now = time::OffsetDateTime::now_utc();
    PublicationPointSnapshot {
        format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
        manifest_rsync_uri: "rsync://example.test/repo/pp/manifest.mft".to_string(),
        publication_point_rsync_uri: "rsync://example.test/repo/pp/".to_string(),
        manifest_number_be: vec![1],
        this_update: PackTime::from_utc_offset_datetime(now),
        next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)),
        verified_at: PackTime::from_utc_offset_datetime(now),
        manifest_bytes,
        files,
    }
}

#[test]
fn process_snapshot_drop_object_on_wrong_issuer_ca_for_roa() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
    let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
    let ee_crldp = roa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();
    let crl_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
            PackFile::from_bytes_compute_sha256(
                "rsync://rpki.cernet.net/repo/cernet/0/AS4538.roa",
                roa_bytes,
            ),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &wrong_issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert_eq!(out.stats.roa_total, 1);
    assert_eq!(out.stats.roa_ok, 0);
    assert_eq!(out.audit.len(), 1);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
    let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
    let ee_crldp = roa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();
    let crl_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    );
    let aspa_bytes = fixture_bytes(
        "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/first.roa",
                roa_bytes.clone(),
            ),
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/second.roa",
                roa_bytes,
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
        ..Policy::default()
    };

    let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &wrong_issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    assert!(out.stats.publication_point_dropped);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_drop_object_on_wrong_issuer_ca_for_aspa() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let aspa_bytes = fixture_bytes(
        "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
    );
    let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
    let ee_crldp = aspa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();
    let crl_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &wrong_issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert_eq!(out.stats.aspa_total, 1);
    assert_eq!(out.stats.aspa_ok, 0);
    assert_eq!(out.audit.len(), 1);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
    let aspa_bytes = fixture_bytes(
        "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
    );
    let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
    let ee_crldp = aspa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();
    let crl_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/y.roa", roa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
        ..Policy::default()
    };

    let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &wrong_issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    assert!(out.stats.publication_point_dropped);
}

#[test]
fn process_snapshot_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
    let aspa_bytes = fixture_bytes(
        "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert!(out.stats.publication_point_dropped);
    assert_eq!(out.stats.roa_total, 1);
    assert_eq!(out.stats.aspa_total, 1);
    assert_eq!(out.vrps.len(), 0);
    assert_eq!(out.aspas.len(), 0);
    assert_eq!(out.audit.len(), 2);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_for_issuer_drop_object_records_errors_and_continues() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.asa", vec![0x00]),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert!(!out.stats.publication_point_dropped);
    assert_eq!(out.stats.roa_total, 1);
    assert_eq!(out.stats.aspa_total, 1);
    assert_eq!(out.audit.len(), 2);
    assert_eq!(out.warnings.len(), 2);
}

#[test]
fn process_snapshot_for_issuer_drop_publication_point_records_skips_for_rest() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/c.asa", vec![0x00]),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert!(out.stats.publication_point_dropped);
    assert_eq!(out.stats.roa_total, 2);
    assert_eq!(out.stats.aspa_total, 1);
    assert!(out.audit.len() >= 3, "expected error + skipped entries");
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
    let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
    let ee_crldp = roa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();

    // Provide a CRL file with the *exact* rsync URI referenced by the embedded EE certificate.
    // Bytes need not be valid for this test: we just want to cover deterministic selection.
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert_eq!(out.stats.roa_total, 1);
    assert_eq!(out.stats.roa_ok, 0, "expected validation to fail later");
    assert_eq!(out.audit.len(), 1);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn process_snapshot_for_issuer_rejects_roa_when_crldp_crl_missing() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");

    // Pack has a CRL, but its URI does not match the embedded EE certificate CRLDP.
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/not-it.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert_eq!(out.stats.roa_total, 1);
    assert_eq!(out.stats.roa_ok, 0);
    assert_eq!(out.audit.len(), 1);
    assert_eq!(out.warnings.len(), 1);
    assert!(
        out.warnings[0].message.contains("dropping invalid ROA")
            || out.warnings[0]
                .message
                .contains("dropping publication point"),
        "expected deterministic CRL selection failure to surface as an invalid ROA warning"
    );
}

#[test]
fn process_snapshot_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let aspa_bytes = fixture_bytes(
        "tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
    );
    let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
    let ee_crldp = aspa.signed_object.signed_data.certificates[0]
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
        .expect("ee crldp")[0]
        .as_str()
        .to_string();

    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes),
        ],
    );

    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };

    let out = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &fixture_bytes("tests/fixtures/ta/apnic-ta.cer"),
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );

    assert_eq!(out.stats.aspa_total, 1);
    assert_eq!(out.stats.aspa_ok, 0);
    assert_eq!(out.audit.len(), 1);
    assert_eq!(out.warnings.len(), 1);
}

#[test]
fn parallel_roa_processing_drop_object_records_roa_and_aspa_errors_like_serial() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/c.asa", vec![0x00]),
        ],
    );
    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };
    let issuer_ca_der = fixture_bytes("tests/fixtures/ta/apnic-ta.cer");

    let serial = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    let phase2_config = ParallelPhase2Config {
        object_workers: 2,
        worker_queue_capacity: 1,
        ..ParallelPhase2Config::default()
    };
    let pool = ParallelRoaWorkerPool::new(&phase2_config).expect("parallel roa pool");
    let parallel = process_publication_point_for_issuer_parallel_roa_with_pool(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
        &pool,
    );
    let parallel_again = process_publication_point_for_issuer_parallel_roa_with_pool(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
        &pool,
    );

    assert_eq!(parallel.stats, serial.stats);
    assert_eq!(parallel_again.stats, serial.stats);
    assert_eq!(parallel.audit, serial.audit);
    assert_eq!(parallel.warnings.len(), serial.warnings.len());
    assert_eq!(parallel.stats.roa_total, 2);
    assert_eq!(parallel.stats.aspa_total, 1);
    assert_eq!(parallel.audit.len(), 3);
}

#[test]
fn parallel_roa_processing_falls_back_for_drop_publication_point_policy() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/b.roa", vec![0x00]),
        ],
    );
    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropPublicationPoint,
        ..Policy::default()
    };
    let issuer_ca_der = fixture_bytes("tests/fixtures/ta/apnic-ta.cer");

    let serial = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    let parallel = process_publication_point_snapshot_for_issuer_parallel_roa(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
        &ParallelPhase2Config {
            object_workers: 2,
            worker_queue_capacity: 1,
            ..ParallelPhase2Config::default()
        },
    );

    assert_eq!(parallel.stats, serial.stats);
    assert_eq!(parallel.audit, serial.audit);
    assert_eq!(parallel.warnings, serial.warnings);
    assert!(parallel.stats.publication_point_dropped);
}

#[test]
fn parallel_roa_processing_falls_back_for_single_worker_config() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
        ],
    );
    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };
    let issuer_ca_der = fixture_bytes("tests/fixtures/ta/apnic-ta.cer");

    let serial = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    let parallel = process_publication_point_snapshot_for_issuer_parallel_roa(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
        &ParallelPhase2Config {
            object_workers: 1,
            worker_queue_capacity: 1,
            ..ParallelPhase2Config::default()
        },
    );

    assert_eq!(parallel.stats, serial.stats);
    assert_eq!(parallel.audit, serial.audit);
    assert_eq!(parallel.warnings, serial.warnings);
}

#[test]
fn parallel_roa_processing_falls_back_when_pool_creation_fails() {
    let manifest_bytes = fixture_bytes(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let pack = dummy_snapshot(
        manifest_bytes,
        vec![
            PackFile::from_bytes_compute_sha256(
                "rsync://example.test/repo/pp/issuer.crl",
                vec![0x01],
            ),
            PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", vec![0x00]),
        ],
    );
    let policy = Policy {
        signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
        ..Policy::default()
    };
    let issuer_ca_der = fixture_bytes("tests/fixtures/ta/apnic-ta.cer");

    let serial = process_publication_point_snapshot_for_issuer(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
    );
    let parallel = process_publication_point_snapshot_for_issuer_parallel_roa(
        &pack,
        &policy,
        &issuer_ca_der,
        None,
        None,
        None,
        time::OffsetDateTime::now_utc(),
        None,
        &ParallelPhase2Config {
            object_workers: 2,
            worker_queue_capacity: 0,
            ..ParallelPhase2Config::default()
        },
    );

    assert_eq!(parallel.stats, serial.stats);
    assert_eq!(parallel.audit, serial.audit);
    assert_eq!(parallel.warnings, serial.warnings);
}
