use std::process::Command;

#[test]
fn report_to_routinator_csv_exports_sorted_rows() {
    let dir = tempfile::tempdir().expect("tempdir");
    let report = dir.path().join("report.json");
    let out_csv = dir.path().join("vrps.csv");
    std::fs::write(
        &report,
        r#"{
  "format_version": 2,
  "meta": {"validation_time_rfc3339_utc": "2026-03-13T02:30:00Z"},
  "policy": {},
  "tree": {"instances_processed": 0, "instances_failed": 0, "warnings": []},
  "publication_points": [],
  "vrps": [
    {"asn": 64497, "prefix": "203.0.113.0/24", "max_length": 24},
    {"asn": 64496, "prefix": "192.0.2.0/24", "max_length": 24}
  ],
  "aspas": [],
  "downloads": [],
  "download_stats": {"events_total": 0, "by_kind": {}}
}"#,
    )
    .expect("write report");

    let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/payload_replay/report_to_routinator_csv.py");
    let out = Command::new("python3")
        .arg(&script)
        .args([
            "--report",
            report.to_string_lossy().as_ref(),
            "--out",
            out_csv.to_string_lossy().as_ref(),
            "--trust-anchor",
            "apnic",
        ])
        .output()
        .expect("run export script");

    assert!(
        out.status.success(),
        "script failed: status={}\nstdout={}\nstderr={}",
        out.status,
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    let csv = std::fs::read_to_string(&out_csv).expect("read csv");
    let lines: Vec<_> = csv.lines().collect();
    assert_eq!(lines[0], "ASN,IP Prefix,Max Length,Trust Anchor");
    assert_eq!(lines[1], "AS64496,192.0.2.0/24,24,apnic");
    assert_eq!(lines[2], "AS64497,203.0.113.0/24,24,apnic");
}

#[test]
fn compare_with_routinator_record_reports_diff_counts() {
    let dir = tempfile::tempdir().expect("tempdir");
    let ours = dir.path().join("ours.csv");
    let record = dir.path().join("record.csv");
    let summary = dir.path().join("summary.md");
    let only_ours = dir.path().join("only_ours.csv");
    let only_record = dir.path().join("only_record.csv");

    std::fs::write(
        &ours,
        "ASN,IP Prefix,Max Length,Trust Anchor\nAS64496,192.0.2.0/24,24,apnic\nAS64497,198.51.100.0/24,24,apnic\n",
    )
    .expect("write ours csv");
    std::fs::write(
        &record,
        "ASN,IP Prefix,Max Length,Trust Anchor\nAS64496,192.0.2.0/24,24,apnic\nAS64498,203.0.113.0/24,24,apnic\n",
    )
    .expect("write record csv");

    let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/payload_replay/compare_with_routinator_record.sh");
    let out = Command::new(&script)
        .args([
            ours.to_string_lossy().as_ref(),
            record.to_string_lossy().as_ref(),
            summary.to_string_lossy().as_ref(),
            only_ours.to_string_lossy().as_ref(),
            only_record.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run compare script");

    assert!(
        out.status.success(),
        "script failed: status={}\nstdout={}\nstderr={}",
        out.status,
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    let summary_text = std::fs::read_to_string(&summary).expect("read summary");
    assert!(
        summary_text.contains("| ours_total | 2 |"),
        "{summary_text}"
    );
    assert!(
        summary_text.contains("| record_total | 2 |"),
        "{summary_text}"
    );
    assert!(
        summary_text.contains("| intersection | 1 |"),
        "{summary_text}"
    );
    assert!(
        summary_text.contains("| only_in_ours | 1 |"),
        "{summary_text}"
    );
    assert!(
        summary_text.contains("| only_in_record | 1 |"),
        "{summary_text}"
    );

    let only_ours_text = std::fs::read_to_string(&only_ours).expect("read only ours csv");
    assert!(only_ours_text.contains("AS64497,198.51.100.0/24,24,apnic"));
    let only_record_text = std::fs::read_to_string(&only_record).expect("read only record csv");
    assert!(only_record_text.contains("AS64498,203.0.113.0/24,24,apnic"));
}

#[test]
fn write_multi_rir_case_report_combines_compare_and_timing() {
    let dir = tempfile::tempdir().expect("tempdir");
    let snapshot_meta = dir.path().join("snapshot_meta.json");
    let delta_meta = dir.path().join("delta_meta.json");
    let snapshot_compare = dir.path().join("snapshot_compare.md");
    let delta_compare = dir.path().join("delta_compare.md");
    let out_md = dir.path().join("case_report.md");
    let out_json = dir.path().join("case_report.json");

    std::fs::write(
        &snapshot_meta,
        r#"{
  "durations_secs": {"rpki_run": 12},
  "counts": {"vrps": 10, "aspas": 1}
}"#,
    )
    .expect("write snapshot meta");
    std::fs::write(
        &delta_meta,
        r#"{
  "durations_secs": {"rpki_run": 8},
  "counts": {"vrps": 11, "aspas": 1}
}"#,
    )
    .expect("write delta meta");
    std::fs::write(
        &snapshot_compare,
        "# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 10 |\n| record_total | 10 |\n| intersection | 10 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n",
    )
    .expect("write snapshot compare");
    std::fs::write(
        &delta_compare,
        "# compare\n\n| metric | value |\n|---|---:|\n| ours_total | 11 |\n| record_total | 11 |\n| intersection | 11 |\n| only_in_ours | 0 |\n| only_in_record | 0 |\n",
    )
    .expect("write delta compare");

    let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/payload_replay/write_multi_rir_case_report.py");
    let out = Command::new("python3")
        .arg(&script)
        .args([
            "--rir",
            "afrinic",
            "--snapshot-meta",
            snapshot_meta.to_string_lossy().as_ref(),
            "--snapshot-compare",
            snapshot_compare.to_string_lossy().as_ref(),
            "--delta-meta",
            delta_meta.to_string_lossy().as_ref(),
            "--delta-compare",
            delta_compare.to_string_lossy().as_ref(),
            "--routinator-base-seconds",
            "6.0",
            "--routinator-delta-seconds",
            "4.0",
            "--out-md",
            out_md.to_string_lossy().as_ref(),
            "--out-json",
            out_json.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run case report script");

    assert!(
        out.status.success(),
        "script failed: status={}\nstdout={}\nstderr={}",
        out.status,
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    let report: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&out_json).expect("read report json"))
            .expect("parse report json");
    assert_eq!(report["rir"].as_str(), Some("afrinic"));
    assert_eq!(report["snapshot"]["match"].as_bool(), Some(true));
    assert_eq!(report["delta"]["match"].as_bool(), Some(true));
    assert_eq!(report["snapshot"]["ratio"].as_f64(), Some(2.0));
    assert_eq!(report["delta"]["ratio"].as_f64(), Some(2.0));

    let md = std::fs::read_to_string(&out_md).expect("read markdown");
    assert!(md.contains("AFRINIC Replay Report"), "{md}");
    assert!(
        md.contains("| snapshot | true | 12.000 | 6.000 | 2.000 | 0 | 0 |"),
        "{md}"
    );
    assert!(
        md.contains("| delta | true | 8.000 | 4.000 | 2.000 | 0 | 0 |"),
        "{md}"
    );
}

#[test]
fn write_multi_rir_summary_aggregates_case_reports() {
    let dir = tempfile::tempdir().expect("tempdir");
    let case_root = dir.path().join("cases");
    for (rir, snapshot_ratio, delta_ratio) in [
        ("afrinic", 2.0_f64, 3.0_f64),
        ("apnic", 1.5_f64, 2.5_f64),
        ("arin", 1.1_f64, 1.6_f64),
        ("lacnic", 3.8_f64, 4.8_f64),
        ("ripe", 2.7_f64, 2.6_f64),
    ] {
        let rir_dir = case_root.join(rir);
        std::fs::create_dir_all(&rir_dir).expect("create rir dir");
        let report = serde_json::json!({
            "rir": rir,
            "snapshot": {
                "match": true,
                "ours_seconds": 10.0,
                "routinator_seconds": 5.0,
                "ratio": snapshot_ratio,
                "compare": {"only_in_ours": 0, "only_in_record": 0}
            },
            "delta": {
                "match": true,
                "ours_seconds": 12.0,
                "routinator_seconds": 6.0,
                "ratio": delta_ratio,
                "compare": {"only_in_ours": 0, "only_in_record": 0}
            }
        });
        std::fs::write(
            rir_dir.join(format!("{rir}_case_report.json")),
            serde_json::to_vec_pretty(&report).expect("serialize report"),
        )
        .expect("write report");
    }

    let out_md = dir.path().join("summary.md");
    let out_json = dir.path().join("summary.json");
    let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/payload_replay/write_multi_rir_summary.py");
    let out = Command::new("python3")
        .arg(&script)
        .args([
            "--case-root",
            case_root.to_string_lossy().as_ref(),
            "--out-md",
            out_md.to_string_lossy().as_ref(),
            "--out-json",
            out_json.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run summary script");

    assert!(
        out.status.success(),
        "script failed: status={}\nstdout={}\nstderr={}",
        out.status,
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    let json: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&out_json).expect("read summary json"))
            .expect("parse summary json");
    assert_eq!(json["cases"].as_array().map(|v| v.len()), Some(5));
    assert_eq!(json["summary"]["snapshot_all_match"].as_bool(), Some(true));
    assert_eq!(json["summary"]["delta_all_match"].as_bool(), Some(true));
    assert!(json["summary"]["all_ratio_geomean"].as_f64().unwrap_or(0.0) > 0.0);

    let md = std::fs::read_to_string(&out_md).expect("read summary md");
    assert!(md.contains("Multi-RIR Replay Summary"), "{md}");
    assert!(
        md.contains("| afrinic | true | 10.000 | 5.000 | 2.000 | true | 12.000 | 6.000 | 3.000 |"),
        "{md}"
    );
}

#[test]
fn apnic_snapshot_profile_script_dry_run_builds_command() {
    let script = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("scripts/payload_replay/run_apnic_snapshot_replay_profile.sh");
    let out = Command::new("bash")
        .env("DRY_RUN", "1")
        .arg(&script)
        .output()
        .expect("run profile script dry-run");

    assert!(
        out.status.success(),
        "script failed: status={}\nstdout={}\nstderr={}",
        out.status,
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );

    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(stdout.contains("--payload-replay-archive"), "{stdout}");
    assert!(stdout.contains("--payload-replay-locks"), "{stdout}");
    assert!(stdout.contains("--analyze"), "{stdout}");
    assert!(stdout.contains("--profile-cpu"), "{stdout}");
}
