use rpki::audit::PublicationPointAudit;
use rpki::policy::{Policy, SignedObjectFailurePolicy};
use rpki::storage::{PackFile, PackTime};
use rpki::validation::manifest::PublicationPointSource;
use rpki::validation::objects::process_publication_point_snapshot_for_issuer;
use rpki::validation::publication_point::PublicationPointSnapshot;
use rpki::validation::tree::{
    CaCertificateRef, CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner,
    TreeRunConfig, run_tree_serial_audit,
};

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(files: Vec<PackFile>) -> PublicationPointSnapshot {
    let now = time::OffsetDateTime::now_utc();
    let manifest_rsync_uri =
        "rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
    PublicationPointSnapshot {
        format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
        manifest_rsync_uri: manifest_rsync_uri.to_string(),
        publication_point_rsync_uri: "rsync://rpki.cernet.net/repo/cernet/0/".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: fixture_bytes(
            "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
        ),
        files,
    }
}

struct SinglePackRunner {
    policy: Policy,
    snapshot: PublicationPointSnapshot,
}

impl PublicationPointRunner for SinglePackRunner {
    fn run_publication_point(
        &self,
        ca: &CaInstanceHandle,
    ) -> Result<PublicationPointRunResult, String> {
        let objects = process_publication_point_snapshot_for_issuer(
            &self.snapshot,
            &self.policy,
            match &ca.ca_certificate {
                CaCertificateRef::InlineDer(bytes) => bytes.as_slice(),
                other => panic!("test runner expects inline CA DER, got {other:?}"),
            },
            ca.ca_certificate_rsync_uri.as_deref(),
            ca.effective_ip_resources.as_ref(),
            ca.effective_as_resources.as_ref(),
            time::OffsetDateTime::now_utc(),
            None,
        );

        Ok(PublicationPointRunResult {
            source: PublicationPointSource::Fresh,
            snapshot: Some(self.snapshot.clone()),
            warnings: Vec::new(),
            objects,
            audit: PublicationPointAudit::default(),
            cir_fresh_objects: Vec::new(),
            cir_cached_objects: Vec::new(),
            discovered_children: Vec::new(),
        })
    }
}

#[test]
fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
    let roa_bytes =
        fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");

    // Include at least one CRL file but with a URI that does NOT match the EE certificate's CRLDP.
    let pack = dummy_snapshot(vec![
        PackFile::from_bytes_compute_sha256("rsync://example.test/repo/not-it.crl", vec![0x01]),
        PackFile::from_bytes_compute_sha256("rsync://example.test/repo/a.roa", roa_bytes),
    ]);

    let mut policy = Policy::default();
    policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;

    let runner = SinglePackRunner {
        policy,
        snapshot: pack,
    };

    let root = CaInstanceHandle {
        depth: 0,
        tal_id: "test-tal".to_string(),
        parent_manifest_rsync_uri: None,
        // Use a real, parseable CA certificate DER so objects processing can reach CRL selection.
        // The test only asserts CRLDP/locked-pack error handling, not signature chaining.
        ca_certificate: CaCertificateRef::inline_der(fixture_bytes(
            "tests/fixtures/ta/apnic-ta.cer",
        )),
        ca_certificate_rsync_uri: None,
        effective_ip_resources: None,
        effective_as_resources: None,
        rsync_base_uri: "rsync://example.test/repo/".to_string(),
        manifest_rsync_uri:
            "rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft"
                .to_string(),
        publication_point_rsync_uri: "rsync://example.test/repo/".to_string(),
        rrdp_notification_uri: None,
    };

    let out = run_tree_serial_audit(
        root,
        &runner,
        &TreeRunConfig {
            max_depth: Some(0),
            max_instances: Some(1),
            compact_audit: false,
            persist_vcir: true,
            build_ccr_accumulator: true,
            enable_roa_validation_cache: false,
            enable_child_certificate_validation_cache: false,
            publication_point_cache_observe_only: false,
            enable_publication_point_validation_cache: false,
            enable_transport_request_prefetch: false,
        },
    )
    .expect("run tree audit");

    assert_eq!(out.tree.instances_processed, 1);
    assert_eq!(out.tree.instances_failed, 0);

    assert!(
        out.tree.warnings.iter().any(|w| w
            .message
            .contains("dropping publication point due to invalid ROA")),
        "expected publication point drop warning"
    );
    let w = out
        .tree
        .warnings
        .iter()
        .find(|w| {
            w.message
                .contains("dropping publication point due to invalid ROA")
        })
        .expect("warning present");
    let refs = w.rfc_refs.iter().map(|r| r.0).collect::<Vec<_>>();
    assert!(
        refs.contains(&"RFC 6487 §4.8.6"),
        "expected CRLDP RFC reference in warning: {refs:?}"
    );
    assert!(
        refs.contains(&"RFC 9286 §4.2.1"),
        "expected manifest locked-pack RFC reference in warning: {refs:?}"
    );

    assert_eq!(out.publication_points.len(), 1);
    assert_eq!(out.publication_points[0].node_id, Some(0));
    assert_eq!(out.publication_points[0].parent_node_id, None);
}
