use std::path::PathBuf;
use std::process::Command;

use rpki::ccr::{
    CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, TrustAnchorState,
    encode_content_info,
};
use rpki::cir::{
    CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
    compute_reject_list_sha256, encode_cir, sha256,
};

fn skip_heavy_blackbox_test() -> bool {
    std::env::var_os("RPKI_SKIP_HEAVY_BLACKBOX_TESTS").is_some()
}

#[test]
fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
    if skip_heavy_blackbox_test() {
        return;
    }
    let out_dir = tempfile::tempdir().expect("tempdir");
    let out = out_dir.path().join("cir-sequence");
    let script = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/cir/run_cir_record_sequence_offline.sh");
    let fixture_root = out_dir.path().join("fixture");
    let static_payload_root = fixture_root.join("payloads");
    std::fs::create_dir_all(&static_payload_root).unwrap();
    let base_locks = fixture_root.join("base-locks.json");
    let delta_locks = fixture_root.join("locks-delta.json");
    std::fs::write(&base_locks, br#"{"validationTime":"2026-03-16T11:49:15Z"}"#).unwrap();
    std::fs::write(
        &delta_locks,
        br#"{"validationTime":"2026-03-16T11:50:15Z"}"#,
    )
    .unwrap();

    let mk_cir = |uri: &str, hash_hex: &str, vt: &str| CanonicalInputRepresentation {
        version: CIR_VERSION_V3,
        hash_alg: CirHashAlgorithm::Sha256,
        validation_time: time::OffsetDateTime::parse(
            vt,
            &time::format_description::well_known::Rfc3339,
        )
        .unwrap(),
        objects: vec![CirObject {
            rsync_uri: uri.to_string(),
            sha256: hex::decode(hash_hex).unwrap(),
        }],
        trust_anchors: vec![test_trust_anchor()],
        reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
        rejected_objects: Vec::new(),
    };
    let full_hash = {
        use sha2::{Digest, Sha256};
        hex::encode(Sha256::digest(b"full-object"))
    };
    let delta_hash = {
        use sha2::{Digest, Sha256};
        hex::encode(Sha256::digest(b"delta-object"))
    };
    let full_cir = mk_cir(
        "rsync://example.net/repo/full.roa",
        &full_hash,
        "2026-03-16T11:49:15Z",
    );
    let delta_cir = CanonicalInputRepresentation {
        version: CIR_VERSION_V3,
        hash_alg: CirHashAlgorithm::Sha256,
        validation_time: time::OffsetDateTime::parse(
            "2026-03-16T11:50:15Z",
            &time::format_description::well_known::Rfc3339,
        )
        .unwrap(),
        objects: {
            let mut objects = vec![
                full_cir.objects[0].clone(),
                CirObject {
                    rsync_uri: "rsync://example.net/repo/delta.roa".to_string(),
                    sha256: hex::decode(&delta_hash).unwrap(),
                },
            ];
            objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
            objects
        },
        trust_anchors: full_cir.trust_anchors.clone(),
        reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
        rejected_objects: Vec::new(),
    };
    let empty_ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
        version: 0,
        hash_alg: CcrDigestAlgorithm::Sha256,
        produced_at: full_cir.validation_time,
        mfts: None,
        vrps: None,
        vaps: None,
        tas: Some(TrustAnchorState {
            skis: vec![vec![0x11; 20]],
            hash: vec![0x22; 32],
        }),
        rks: None,
    });
    let full_cir_path = fixture_root.join("full.cir");
    let delta_cir_path = fixture_root.join("delta.cir");
    let full_ccr_path = fixture_root.join("full.ccr");
    let delta_ccr_path = fixture_root.join("delta.ccr");
    let full_report_path = fixture_root.join("full-report.json");
    let delta_report_path = fixture_root.join("delta-report.json");
    std::fs::create_dir_all(&fixture_root).unwrap();
    std::fs::write(&full_cir_path, encode_cir(&full_cir).unwrap()).unwrap();
    std::fs::write(&delta_cir_path, encode_cir(&delta_cir).unwrap()).unwrap();
    std::fs::write(&full_ccr_path, encode_content_info(&empty_ccr).unwrap()).unwrap();
    std::fs::write(&delta_ccr_path, encode_content_info(&empty_ccr).unwrap()).unwrap();
    std::fs::write(
        &full_report_path,
        br#"{"format_version":2,"publication_points":[]}"#,
    )
    .unwrap();
    std::fs::write(
        &delta_report_path,
        br#"{"format_version":2,"publication_points":[]}"#,
    )
    .unwrap();
    std::fs::write(static_payload_root.join("full-object"), b"full-object").unwrap();
    std::fs::write(static_payload_root.join("delta-object"), b"delta-object").unwrap();

    let stub = out_dir.path().join("stub-rpki.sh");
    std::fs::write(
        &stub,
        format!(
            r#"#!/usr/bin/env bash
set -euo pipefail
MODE=""
cir=""
ccr=""
report=""
repo_bytes_db=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --payload-replay-archive) MODE="full"; shift 2 ;;
    --payload-base-archive) MODE="delta"; shift 2 ;;
    --cir-out) cir="$2"; shift 2 ;;
    --ccr-out) ccr="$2"; shift 2 ;;
    --report-json) report="$2"; shift 2 ;;
    --repo-bytes-db) repo_bytes_db="$2"; shift 2 ;;
    *) shift ;;
  esac
done
mkdir -p "$(dirname "$cir")" "$(dirname "$ccr")" "$(dirname "$report")" "$repo_bytes_db"
if [[ "$MODE" == "full" ]]; then
  cp "{full_cir}" "$cir"
  cp "{full_ccr}" "$ccr"
  cp "{full_report}" "$report"
else
  if [[ "$cir" == *delta-001* ]]; then
    cp "{delta_cir}" "$cir"
    cp "{delta_ccr}" "$ccr"
    cp "{delta_report}" "$report"
  else
    cp "{delta_cir}" "$cir"
    cp "{delta_ccr}" "$ccr"
    cp "{delta_report}" "$report"
  fi
fi
"#,
            full_cir = full_cir_path.display(),
            delta_cir = delta_cir_path.display(),
            full_ccr = full_ccr_path.display(),
            delta_ccr = delta_ccr_path.display(),
            full_report = full_report_path.display(),
            delta_report = delta_report_path.display(),
        ),
    )
    .unwrap();
    std::fs::set_permissions(&stub, std::os::unix::fs::PermissionsExt::from_mode(0o755)).unwrap();

    let proc = Command::new(script)
        .args([
            "--out-dir",
            out.to_string_lossy().as_ref(),
            "--tal-path",
            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("tests/fixtures/tal/apnic-rfc7730-https.tal")
                .to_string_lossy()
                .as_ref(),
            "--ta-path",
            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("tests/fixtures/ta/apnic-ta.cer")
                .to_string_lossy()
                .as_ref(),
            "--cir-tal-uri",
            "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal",
            "--payload-replay-archive",
            "/tmp/base-payload-archive",
            "--payload-replay-locks",
            base_locks.to_string_lossy().as_ref(),
            "--payload-base-archive",
            "/tmp/base-payload-archive",
            "--payload-base-locks",
            base_locks.to_string_lossy().as_ref(),
            "--payload-delta-archive",
            "/tmp/payload-delta-archive",
            "--payload-delta-locks",
            delta_locks.to_string_lossy().as_ref(),
            "--delta-count",
            "2",
            "--max-depth",
            "0",
            "--max-instances",
            "1",
            "--rpki-bin",
            stub.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run cir sequence script");
    assert!(
        proc.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&proc.stderr)
    );

    let sequence_json: serde_json::Value =
        serde_json::from_slice(&std::fs::read(out.join("sequence.json")).unwrap()).unwrap();
    let steps = sequence_json["steps"].as_array().expect("steps");
    assert_eq!(steps.len(), 3);
    assert_eq!(steps[0]["stepId"], "full");
    assert_eq!(steps[1]["stepId"], "delta-001");
    assert_eq!(steps[2]["stepId"], "delta-002");

    for rel in [
        "full/input.cir",
        "full/result.ccr",
        "full/report.json",
        "delta-001/input.cir",
        "delta-001/result.ccr",
        "delta-001/report.json",
        "delta-002/input.cir",
        "delta-002/result.ccr",
        "delta-002/report.json",
    ] {
        assert!(out.join(rel).is_file(), "missing {}", rel);
    }
}

fn test_trust_anchor() -> CirTrustAnchor {
    let ta_rsync_uri = "rsync://example.net/repo/root.cer";
    let ta_certificate_der = b"ta-der".to_vec();
    CirTrustAnchor {
        ta_rsync_uri: ta_rsync_uri.to_string(),
        tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
        tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
        ta_certificate_sha256: sha256(&ta_certificate_der),
        ta_certificate_der,
    }
}
