use std::env;
use std::fs::File;
use std::path::PathBuf;

use anyhow::{Context, Result, anyhow};
use serde::Serialize;

use rpki::rtr::payload::Payload;
use rpki::slurm::file::SlurmFile;
use rpki::source::ccr::{
    find_latest_ccr_file, load_ccr_snapshot_from_file, snapshot_to_payloads_with_options,
};

#[derive(Debug)]
struct Cli {
    ccr_path: PathBuf,
    slurm_path: PathBuf,
    strict_ccr: bool,
    dump_payloads: bool,
}

#[derive(Debug, Serialize)]
struct Output {
    ccr_path: String,
    slurm_path: String,
    produced_at: Option<String>,
    slurm_version: u32,
    input_payload_count: usize,
    input_vrp_count: usize,
    input_vap_count: usize,
    output_payload_count: usize,
    output_vrp_count: usize,
    output_vap_count: usize,
    invalid_vrps: Vec<String>,
    invalid_vaps: Vec<String>,
    sample_output_aspa_customers: Vec<u32>,
    payloads: Option<Vec<Payload>>,
}

fn main() -> Result<()> {
    let cli = parse_args(env::args().skip(1))?;

    let snapshot = load_ccr_snapshot_from_file(&cli.ccr_path)
        .with_context(|| format!("failed to load CCR snapshot: {}", cli.ccr_path.display()))?;
    let slurm = load_slurm(&cli.slurm_path)?;

    let conversion = snapshot_to_payloads_with_options(&snapshot, cli.strict_ccr)?;
    let payloads = slurm.apply(&conversion.payloads);
    let (input_vrp_count, input_vap_count) = count_vrps_and_vaps(&conversion.payloads);
    let (output_vrp_count, output_vap_count) = count_vrps_and_vaps(&payloads);

    let output = Output {
        ccr_path: cli.ccr_path.display().to_string(),
        slurm_path: cli.slurm_path.display().to_string(),
        produced_at: snapshot.produced_at.clone(),
        slurm_version: slurm.version().as_u32(),
        input_payload_count: conversion.payloads.len(),
        input_vrp_count,
        input_vap_count,
        output_payload_count: payloads.len(),
        output_vrp_count,
        output_vap_count,
        invalid_vrps: conversion.invalid_vrps,
        invalid_vaps: conversion.invalid_vaps,
        sample_output_aspa_customers: sample_aspa_customers(&payloads, 8),
        payloads: cli.dump_payloads.then_some(payloads),
    };

    println!("{}", serde_json::to_string_pretty(&output)?);
    Ok(())
}

fn load_slurm(path: &PathBuf) -> Result<SlurmFile> {
    let file = File::open(path)
        .with_context(|| format!("failed to open SLURM file: {}", path.display()))?;
    SlurmFile::from_reader(file)
        .with_context(|| format!("failed to parse SLURM file: {}", path.display()))
}

fn parse_args(args: impl Iterator<Item = String>) -> Result<Cli> {
    let mut strict_ccr = false;
    let mut dump_payloads = false;
    let mut positionals = Vec::new();

    for arg in args {
        match arg.as_str() {
            "--strict-ccr" => strict_ccr = true,
            "--dump-payloads" => dump_payloads = true,
            "-h" | "--help" => {
                print_help();
                std::process::exit(0);
            }
            _ if arg.starts_with('-') => {
                return Err(anyhow!("unknown option: {}", arg));
            }
            _ => positionals.push(arg),
        }
    }

    if positionals.is_empty() {
        return Ok(Cli {
            ccr_path: find_latest_ccr_file("data")
                .context("failed to find latest .ccr in ./data for default run")?,
            slurm_path: PathBuf::from("data/example.slurm"),
            strict_ccr,
            dump_payloads,
        });
    }

    if positionals.len() != 2 {
        print_help();
        return Err(anyhow!(
            "expected: slurm_apply_client <snapshot.ccr> <policy.slurm>"
        ));
    }

    Ok(Cli {
        ccr_path: PathBuf::from(&positionals[0]),
        slurm_path: PathBuf::from(&positionals[1]),
        strict_ccr,
        dump_payloads,
    })
}

fn print_help() {
    eprintln!(
        "Usage: cargo run --bin slurm_apply_client -- [--strict-ccr] [--dump-payloads] <snapshot.ccr> <policy.slurm>"
    );
    eprintln!();
    eprintln!("Reads a CCR snapshot, converts it into payloads, applies SLURM, and prints JSON.");
    eprintln!(
        "When no arguments are provided, it defaults to the latest .ccr under ./data and ./data/example.slurm."
    );
    eprintln!("Use --dump-payloads to include the full payload list in the JSON output.");
}

fn count_vrps_and_vaps(payloads: &[Payload]) -> (usize, usize) {
    let mut vrps = 0;
    let mut vaps = 0;

    for payload in payloads {
        match payload {
            Payload::RouteOrigin(_) => vrps += 1,
            Payload::Aspa(_) => vaps += 1,
            Payload::RouterKey(_) => {}
        }
    }

    (vrps, vaps)
}

fn sample_aspa_customers(payloads: &[Payload], limit: usize) -> Vec<u32> {
    let mut customers = Vec::new();

    for payload in payloads {
        if let Payload::Aspa(aspa) = payload {
            let customer = aspa.customer_asn().into_u32();
            if !customers.contains(&customer) {
                customers.push(customer);
                if customers.len() == limit {
                    break;
                }
            }
        }
    }

    customers
}
