use std::path::Path;

use sha2::Digest;

use rpki::data_model::manifest::ManifestObject;
use rpki::policy::{CaFailedFetchPolicy, Policy};
use rpki::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};
use rpki::validation::manifest::process_manifest_publication_point;

fn issuer_ca_fixture() -> Vec<u8> {
    std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read issuer ca fixture")
}

fn issuer_ca_rsync_uri() -> &'static str {
    "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"
}

fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
    let sha256_hex = hex::encode(sha2::Sha256::digest(&bytes));
    let mut raw_entry = RawByHashEntry::from_bytes(sha256_hex.clone(), bytes);
    raw_entry.origin_uris.push(rsync_uri.to_string());
    raw_entry.object_type = Some(object_type.to_string());
    raw_entry.encoding = Some("der".to_string());
    store
        .put_raw_by_hash_entry(&raw_entry)
        .expect("store raw_by_hash");
    store
        .put_repository_view_entry(&RepositoryViewEntry {
            rsync_uri: rsync_uri.to_string(),
            current_hash: Some(sha256_hex),
            repository_source: Some("https://example.test/notification.xml".to_string()),
            object_type: Some(object_type.to_string()),
            state: RepositoryViewState::Present,
        })
        .expect("store repository view");
}

#[test]
fn manifest_outside_publication_point_is_failed_fetch_rfc9286_section6_1() {
    let fixture_manifest_path = Path::new(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
    );
    let fixture_dir = fixture_manifest_path.parent().expect("fixture dir");

    let manifest_bytes = std::fs::read(fixture_manifest_path).expect("read manifest fixture");
    let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest fixture");
    let validation_time = manifest.manifest.this_update + time::Duration::seconds(1);

    // Intentionally mismatch: manifest is NOT under the publication point URI.
    let manifest_rsync_uri = "rsync://example.test/a/manifest.mft";
    let publication_point_rsync_uri = "rsync://example.test/b/";

    let temp = tempfile::tempdir().expect("tempdir");
    let store = RocksStore::open(temp.path()).expect("open rocksdb");

    // Store the manifest at its rsync URI.
    put_current_object(&store, manifest_rsync_uri, manifest_bytes.clone(), "mft");

    // Store all referenced files under the (different) publication point so that §6.4/§6.5
    // would otherwise succeed if §6.1 was not enforced.
    let entries = manifest
        .manifest
        .parse_files()
        .expect("parse validated manifest fileList");
    for entry in &entries {
        let file_path = fixture_dir.join(entry.file_name.as_str());
        let bytes = std::fs::read(&file_path)
            .unwrap_or_else(|_| panic!("read fixture file referenced by manifest: {file_path:?}"));
        let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
        let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
        put_current_object(&store, &rsync_uri, bytes, object_type);
    }

    let mut policy = Policy::default();
    policy.ca_failed_fetch_policy = CaFailedFetchPolicy::StopAllOutput;
    let issuer_ca_der = issuer_ca_fixture();

    let err = process_manifest_publication_point(
        &store,
        &policy,
        manifest_rsync_uri,
        publication_point_rsync_uri,
        &issuer_ca_der,
        Some(issuer_ca_rsync_uri()),
        validation_time,
    )
    .expect_err("§6.1 mismatch must be treated as failed fetch");
    let msg = err.to_string();
    assert!(msg.contains("RFC 9286 §6.1"), "{msg}");

    assert!(
        store
            .get_vcir(manifest_rsync_uri)
            .expect("get vcir")
            .is_none(),
        "must not write VCIR on failed fetch"
    );
}
