use std::path::PathBuf;

fn usage() -> &'static str {
    "Usage: verification_ccr_compare --source-ccr <path> --verification-ccr <path> --out-json <path> [--out-md <path>]"
}

#[derive(Debug, PartialEq, Eq)]
struct Args {
    source_ccr: PathBuf,
    verification_ccr: PathBuf,
    out_json: PathBuf,
    out_md: Option<PathBuf>,
}

fn parse_args(argv: &[String]) -> Result<Args, String> {
    let mut source_ccr = None;
    let mut verification_ccr = None;
    let mut out_json = None;
    let mut out_md = None;
    let mut index = 1usize;
    while index < argv.len() {
        let target = match argv[index].as_str() {
            "--source-ccr" => &mut source_ccr,
            "--verification-ccr" => &mut verification_ccr,
            "--out-json" => &mut out_json,
            "--out-md" => &mut out_md,
            "-h" | "--help" => return Err(usage().to_string()),
            other => return Err(format!("unknown argument: {other}\n{}", usage())),
        };
        index += 1;
        *target =
            Some(PathBuf::from(argv.get(index).ok_or_else(|| {
                format!("{} requires a value", argv[index - 1])
            })?));
        index += 1;
    }
    let source_ccr = source_ccr.ok_or_else(|| format!("--source-ccr is required\n{}", usage()))?;
    let verification_ccr =
        verification_ccr.ok_or_else(|| format!("--verification-ccr is required\n{}", usage()))?;
    let out_json = out_json.ok_or_else(|| format!("--out-json is required\n{}", usage()))?;
    Ok(Args {
        source_ccr,
        verification_ccr,
        out_json,
        out_md,
    })
}

fn run(args: &Args) -> Result<(), String> {
    let comparison =
        rpki::verification_only::compare_ccr_files(&args.source_ccr, &args.verification_ccr)?;
    rpki::verification_only::write_ccr_comparison(
        &args.out_json,
        args.out_md.as_deref(),
        &comparison,
    )?;
    if !comparison.state_digest_match {
        return Err(format!(
            "CCR state digest mismatch: {}",
            comparison.mismatched_states.join(",")
        ));
    }
    println!("CCR state digests match: {}", args.out_json.display());
    Ok(())
}

fn main() -> Result<(), String> {
    let argv = std::env::args().collect::<Vec<_>>();
    run(&parse_args(&argv)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    use rpki::ccr::{
        CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation,
        build_roa_payload_state, encode_content_info,
    };
    use rpki::data_model::roa::{IpPrefix, RoaAfi};
    use rpki::validation::objects::Vrp;
    use std::fs;

    fn argv(values: &[&str]) -> Vec<String> {
        std::iter::once("verification_ccr_compare")
            .chain(values.iter().copied())
            .map(str::to_string)
            .collect()
    }

    fn sample_ccr(asn: u32) -> Vec<u8> {
        let vrps = build_roa_payload_state(&[Vrp {
            asn,
            prefix: IpPrefix {
                afi: RoaAfi::Ipv4,
                prefix_len: 24,
                addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            },
            max_length: 24,
        }])
        .expect("build VRP state");
        encode_content_info(&CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
            version: 0,
            hash_alg: CcrDigestAlgorithm::Sha256,
            produced_at: time::OffsetDateTime::UNIX_EPOCH,
            mfts: None,
            vrps: Some(vrps),
            vaps: None,
            tas: None,
            rks: None,
        }))
        .expect("encode CCR")
    }

    #[test]
    fn parse_args_accepts_complete_input_and_rejects_bad_forms() {
        let parsed = parse_args(&argv(&[
            "--source-ccr",
            "source.ccr",
            "--verification-ccr",
            "verification.ccr",
            "--out-json",
            "result.json",
            "--out-md",
            "result.md",
        ]))
        .expect("parse");
        assert_eq!(parsed.source_ccr, PathBuf::from("source.ccr"));
        assert_eq!(parsed.out_md, Some(PathBuf::from("result.md")));
        assert!(parse_args(&argv(&[])).unwrap_err().contains("source-ccr"));
        assert!(
            parse_args(&argv(&["--source-ccr"]))
                .unwrap_err()
                .contains("requires a value")
        );
        assert!(
            parse_args(&argv(&["--unknown"]))
                .unwrap_err()
                .contains("unknown argument")
        );
        assert!(parse_args(&argv(&["--help"])).is_err());
    }

    #[test]
    fn run_writes_matching_outputs_and_rejects_mismatch() {
        let temp = tempfile::tempdir().expect("tempdir");
        let source = temp.path().join("source.ccr");
        let verification = temp.path().join("verification.ccr");
        let out_json = temp.path().join("out/result.json");
        let out_md = temp.path().join("out/result.md");
        fs::write(&source, sample_ccr(64496)).expect("source");
        fs::write(&verification, sample_ccr(64496)).expect("verification");
        let args = Args {
            source_ccr: source.clone(),
            verification_ccr: verification.clone(),
            out_json: out_json.clone(),
            out_md: Some(out_md.clone()),
        };
        run(&args).expect("matching run");
        assert!(out_json.is_file());
        assert!(out_md.is_file());

        fs::write(&verification, sample_ccr(64497)).expect("mismatch");
        let error = run(&args).unwrap_err();
        assert!(error.contains("vrps"));
        assert!(
            fs::read_to_string(out_json)
                .expect("comparison JSON")
                .contains("mismatchedStates")
        );
    }
}
