use rpki::ccr::{decode_content_info, extract_vrp_rows};
use std::io::Write;

#[derive(Default, Debug)]
struct Args {
    ccr_path: Option<std::path::PathBuf>,
    out_path: Option<std::path::PathBuf>,
}

fn usage() -> &'static str {
    "Usage: ccr_to_routinator_csv --ccr <path> --out <path> [--trust-anchor <ignored>]"
}

fn parse_args(argv: &[String]) -> Result<Args, String> {
    let mut args = Args::default();
    let mut i = 1usize;
    while i < argv.len() {
        match argv[i].as_str() {
            "--ccr" => {
                i += 1;
                let v = argv.get(i).ok_or("--ccr requires a value")?;
                args.ccr_path = Some(v.into());
            }
            "--out" => {
                i += 1;
                let v = argv.get(i).ok_or("--out requires a value")?;
                args.out_path = Some(v.into());
            }
            "--trust-anchor" => {
                i += 1;
                argv.get(i).ok_or("--trust-anchor requires a value")?;
            }
            "-h" | "--help" => return Err(usage().to_string()),
            other => return Err(format!("unknown argument: {other}\n{}", usage())),
        }
        i += 1;
    }
    if args.ccr_path.is_none() {
        return Err(format!("--ccr is required\n{}", usage()));
    }
    if args.out_path.is_none() {
        return Err(format!("--out is required\n{}", usage()));
    }
    Ok(args)
}

fn collect_vrp_rows(
    bytes: &[u8],
) -> Result<std::collections::BTreeSet<(u32, String, u16)>, String> {
    let content_info = decode_content_info(bytes).map_err(|e| e.to_string())?;
    extract_vrp_rows(&content_info).map_err(|e| e.to_string())
}

fn main() -> Result<(), String> {
    let argv: Vec<String> = std::env::args().collect();
    let args = parse_args(&argv)?;
    let ccr_path = args.ccr_path.as_ref().unwrap();
    let out_path = args.out_path.as_ref().unwrap();
    let bytes = std::fs::read(ccr_path)
        .map_err(|e| format!("read ccr failed: {}: {e}", ccr_path.display()))?;
    let rows = collect_vrp_rows(&bytes)?;
    if let Some(parent) = out_path.parent() {
        std::fs::create_dir_all(parent)
            .map_err(|e| format!("create parent dirs failed: {}: {e}", parent.display()))?;
    }
    let mut file = std::io::BufWriter::new(
        std::fs::File::create(out_path)
            .map_err(|e| format!("create output failed: {}: {e}", out_path.display()))?,
    );
    writeln!(file, "ASN,IP Prefix,Max Length,Trust Anchor").map_err(|e| e.to_string())?;
    for (asn, prefix, max_len) in rows {
        writeln!(file, "AS{asn},{prefix},{max_len},unknown").map_err(|e| e.to_string())?;
    }
    println!("{}", out_path.display());
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_args_accepts_required_flags() {
        let argv = vec![
            "ccr_to_routinator_csv".to_string(),
            "--ccr".to_string(),
            "a.ccr".to_string(),
            "--out".to_string(),
            "out.csv".to_string(),
            "--trust-anchor".to_string(),
            "apnic".to_string(),
        ];
        let args = parse_args(&argv).expect("parse args");
        assert_eq!(
            args.ccr_path.as_deref(),
            Some(std::path::Path::new("a.ccr"))
        );
        assert_eq!(
            args.out_path.as_deref(),
            Some(std::path::Path::new("out.csv"))
        );
    }

    #[test]
    fn parse_args_rejects_missing_required_flags() {
        let argv = vec![
            "ccr_to_routinator_csv".to_string(),
            "--ccr".to_string(),
            "a.ccr".to_string(),
        ];
        let err = parse_args(&argv).unwrap_err();
        assert!(err.contains("--out is required"), "{err}");
    }
}
