use std::path::{Path, PathBuf};
use std::process::Command;

use rpki::blob_store::ExternalRepoBytesDb;
use rpki::cir::{
    CanonicalInputRepresentation, CirTrustAnchor, encode_cir, materialize_cir_from_repo_bytes,
    sha256,
};

fn skip_heavy_script_replay_test() -> bool {
    std::env::var_os("RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS").is_some()
}

fn apnic_tal_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/tal/apnic-rfc7730-https.tal")
}

fn apnic_ta_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer")
}

fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
    let tal_bytes = std::fs::read(apnic_tal_path()).expect("read tal");
    let ta_bytes = std::fs::read(apnic_ta_path()).expect("read ta");
    let tal = rpki::data_model::tal::Tal::decode_bytes(&tal_bytes).expect("decode tal");
    let ta_rsync_uri = tal
        .ta_uris
        .iter()
        .find(|uri| uri.scheme() == "rsync")
        .expect("tal has rsync uri")
        .as_str()
        .to_string();
    (
        CanonicalInputRepresentation::new_v4(
            time::OffsetDateTime::parse(
                "2026-04-07T00:00:00Z",
                &time::format_description::well_known::Rfc3339,
            )
            .unwrap(),
            Vec::new(),
            Vec::new(),
            vec![CirTrustAnchor {
                ta_rsync_uri,
                tal_uri: "https://example.test/root.tal".to_string(),
                tal_bytes,
                ta_certificate_sha256: sha256(&ta_bytes),
                ta_certificate_der: ta_bytes.clone(),
            }],
            Vec::new(),
            Vec::new(),
        ),
        ta_bytes,
    )
}

fn write_repo_bytes(repo_bytes_db: &Path, bytes: &[u8]) {
    use sha2::{Digest, Sha256};
    let hash = hex::encode(Sha256::digest(bytes));
    ExternalRepoBytesDb::open(repo_bytes_db)
        .expect("open repo bytes")
        .put_blob_bytes_batch(&[(hash, bytes.to_vec())])
        .expect("write repo bytes");
}

fn prepare_reference_ccr(
    work: &Path,
    cir: &CanonicalInputRepresentation,
    mirror_root: &Path,
) -> PathBuf {
    let reference_ccr = work.join("reference.ccr");
    let rpki_bin = env!("CARGO_BIN_EXE_rpki");
    let wrapper = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper");
    let tal_path = apnic_tal_path();
    let ta_path = apnic_ta_path();
    let out = Command::new(rpki_bin)
        .env("REAL_RSYNC_BIN", "/usr/bin/rsync")
        .env("CIR_MIRROR_ROOT", mirror_root)
        .env("CIR_LOCAL_LINK_MODE", "1")
        .args([
            "--db",
            work.join("reference-db").to_string_lossy().as_ref(),
            "--tal-path",
            tal_path.to_string_lossy().as_ref(),
            "--ta-path",
            ta_path.to_string_lossy().as_ref(),
            "--disable-rrdp",
            "--rsync-command",
            wrapper.to_string_lossy().as_ref(),
            "--validation-time",
            &cir.validation_time
                .format(&time::format_description::well_known::Rfc3339)
                .unwrap(),
            "--max-depth",
            "0",
            "--max-instances",
            "1",
            "--ccr-out",
            reference_ccr.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run reference rpki");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    reference_ccr
}

fn prepare_sequence_root(td: &Path) -> PathBuf {
    let sequence_root = td.join("sequence");
    let repo_bytes_db = sequence_root.join("repo-bytes.db");
    let mirror_root = td.join("mirror");
    std::fs::create_dir_all(sequence_root.join("full")).unwrap();
    std::fs::create_dir_all(sequence_root.join("delta-001")).unwrap();
    std::fs::create_dir_all(sequence_root.join("delta-002")).unwrap();

    let (cir, ta_bytes) = build_ta_only_cir();
    let cir_bytes = encode_cir(&cir).expect("encode cir");
    std::fs::write(sequence_root.join("full").join("input.cir"), &cir_bytes).unwrap();
    std::fs::write(
        sequence_root.join("delta-001").join("input.cir"),
        &cir_bytes,
    )
    .unwrap();
    std::fs::write(
        sequence_root.join("delta-002").join("input.cir"),
        &cir_bytes,
    )
    .unwrap();
    write_repo_bytes(&repo_bytes_db, &ta_bytes);
    materialize_cir_from_repo_bytes(&cir, &repo_bytes_db, &mirror_root, true).unwrap();
    let reference = prepare_reference_ccr(td, &cir, &mirror_root);
    std::fs::copy(&reference, sequence_root.join("full").join("result.ccr")).unwrap();
    std::fs::copy(
        &reference,
        sequence_root.join("delta-001").join("result.ccr"),
    )
    .unwrap();
    std::fs::copy(
        &reference,
        sequence_root.join("delta-002").join("result.ccr"),
    )
    .unwrap();
    std::fs::write(sequence_root.join("full").join("report.json"), b"{}").unwrap();
    std::fs::write(sequence_root.join("delta-001").join("report.json"), b"{}").unwrap();
    std::fs::write(sequence_root.join("delta-002").join("report.json"), b"{}").unwrap();
    let sequence = serde_json::json!({
        "version": 1,
        "repoBytesDbPath": "repo-bytes.db",
        "steps": [
            {"stepId":"full","kind":"full","validationTime":"2026-04-07T00:00:00Z","cirPath":"full/input.cir","ccrPath":"full/result.ccr","reportPath":"full/report.json","previousStepId":null},
            {"stepId":"delta-001","kind":"delta","validationTime":"2026-04-07T00:00:00Z","cirPath":"delta-001/input.cir","ccrPath":"delta-001/result.ccr","reportPath":"delta-001/report.json","previousStepId":"full"},
            {"stepId":"delta-002","kind":"delta","validationTime":"2026-04-07T00:00:00Z","cirPath":"delta-002/input.cir","ccrPath":"delta-002/result.ccr","reportPath":"delta-002/report.json","previousStepId":"delta-001"}
        ]
    });
    std::fs::write(
        sequence_root.join("sequence.json"),
        serde_json::to_vec_pretty(&sequence).unwrap(),
    )
    .unwrap();
    sequence_root
}

#[test]
fn peer_sequence_replay_scripts_replay_all_steps() {
    if skip_heavy_script_replay_test() {
        return;
    }
    if !Path::new("/usr/bin/rsync").exists()
        || !Path::new("/home/yuyr/dev/rust_playground/routinator/target/debug/routinator").exists()
        || !Path::new("/home/yuyr/dev/rpki-client-9.7/build-m5/src/rpki-client").exists()
    {
        return;
    }

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

    let routinator_script = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/cir/run_cir_replay_sequence_routinator.sh");
    let out = Command::new(routinator_script)
        .env("CIR_MATERIALIZE_BIN", env!("CARGO_BIN_EXE_cir_materialize"))
        .env(
            "CIR_EXTRACT_INPUTS_BIN",
            env!("CARGO_BIN_EXE_cir_extract_inputs"),
        )
        .env(
            "CCR_TO_COMPARE_VIEWS_BIN",
            env!("CARGO_BIN_EXE_ccr_to_compare_views"),
        )
        .args(["--sequence-root", sequence_root.to_string_lossy().as_ref()])
        .output()
        .expect("run routinator sequence replay");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let r_summary: serde_json::Value = serde_json::from_slice(
        &std::fs::read(sequence_root.join("sequence-summary-routinator.json")).unwrap(),
    )
    .unwrap();
    assert_eq!(r_summary["stepCount"], 3);
    assert_eq!(r_summary["allMatch"], true);

    let rpki_client_script = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/cir/run_cir_replay_sequence_rpki_client.sh");
    let out = Command::new(rpki_client_script)
        .env("CIR_MATERIALIZE_BIN", env!("CARGO_BIN_EXE_cir_materialize"))
        .env(
            "CIR_EXTRACT_INPUTS_BIN",
            env!("CARGO_BIN_EXE_cir_extract_inputs"),
        )
        .env(
            "CCR_TO_COMPARE_VIEWS_BIN",
            env!("CARGO_BIN_EXE_ccr_to_compare_views"),
        )
        .args([
            "--sequence-root",
            sequence_root.to_string_lossy().as_ref(),
            "--build-dir",
            "/home/yuyr/dev/rpki-client-9.7/build-m5",
        ])
        .output()
        .expect("run rpki-client sequence replay");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let c_summary: serde_json::Value = serde_json::from_slice(
        &std::fs::read(sequence_root.join("sequence-summary-rpki-client.json")).unwrap(),
    )
    .unwrap();
    assert_eq!(c_summary["stepCount"], 3);
    assert_eq!(c_summary["allMatch"], true);
}
