use std::path::Path;

use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::ResourceCertificate;
use rpki::fetch::rsync::LocalDirRsyncFetcher;
use rpki::policy::{Policy, SyncPreference};
use rpki::storage::RocksStore;
use rpki::sync::rrdp::Fetcher;
use rpki::validation::manifest::PublicationPointSource;
use rpki::validation::run::run_publication_point_once;

fn fixture_to_rsync_uri(path: &Path) -> String {
    let rel = path
        .strip_prefix("tests/fixtures/repository")
        .expect("path under tests/fixtures/repository");
    let mut it = rel.components();
    let host = it
        .next()
        .expect("host component")
        .as_os_str()
        .to_string_lossy();
    let rest = it.as_path().to_string_lossy();
    format!("rsync://{host}/{rest}")
}

fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
    let mut s = fixture_to_rsync_uri(dir);
    if !s.ends_with('/') {
        s.push('/');
    }
    s
}

struct NeverHttpFetcher;

impl Fetcher for NeverHttpFetcher {
    fn fetch(&self, _uri: &str) -> Result<Vec<u8>, String> {
        Err("http fetch disabled in offline test".to_string())
    }
}

#[test]
fn e2e_offline_uses_rsync_then_writes_latest_vcir_then_outputs_vrps() {
    let fixture_dir = Path::new("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
    let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/";
    let manifest_path = fixture_dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft");
    let manifest_rsync_uri = fixture_to_rsync_uri(&manifest_path);
    let publication_point_rsync_uri = fixture_dir_to_rsync_uri(fixture_dir);

    let issuer_ca_der = std::fs::read("tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer")
        .expect("read issuer ca");
    let issuer_ca = ResourceCertificate::decode_der(&issuer_ca_der).expect("decode issuer ca");

    // Choose a validation_time that is safely inside:
    // - manifest thisUpdate..nextUpdate
    // - issuer CA validity
    // - CRL thisUpdate..nextUpdate
    let manifest_der = std::fs::read(&manifest_path).expect("read manifest");
    let manifest = ManifestObject::decode_der(&manifest_der).expect("decode manifest");

    let crl_der = std::fs::read(fixture_dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl"))
        .expect("read crl");
    let crl = RpkixCrl::decode_der(&crl_der).expect("decode crl");

    let mut t = manifest.manifest.this_update;
    if issuer_ca.tbs.validity_not_before > t {
        t = issuer_ca.tbs.validity_not_before;
    }
    if crl.this_update.utc > t {
        t = crl.this_update.utc;
    }
    t += time::Duration::seconds(1);

    let mut policy = Policy::default();
    policy.sync_preference = SyncPreference::RsyncOnly;

    let rsync_fetcher = LocalDirRsyncFetcher::new(fixture_dir);
    let http_fetcher = NeverHttpFetcher;

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

    let out = run_publication_point_once(
        &store,
        &policy,
        None,
        rsync_base_uri,
        &manifest_rsync_uri,
        &publication_point_rsync_uri,
        &http_fetcher,
        &rsync_fetcher,
        &issuer_ca_der,
        None,
        issuer_ca.tbs.extensions.ip_resources.as_ref(),
        issuer_ca.tbs.extensions.as_resources.as_ref(),
        t,
    )
    .expect("run publication point once");

    assert!(
        store
            .get_vcir(&manifest_rsync_uri)
            .expect("get vcir")
            .is_some()
    );
    assert_eq!(out.publication_point_source, PublicationPointSource::Fresh);
    assert!(out.publication_point_warnings.is_empty());
    assert!(
        out.objects.vrps.iter().any(|v| v.asn == 4538),
        "expected VRPs for AS4538"
    );
}
