//! Read a raw capture and emit a repository-safe, replayable fixture.
//!
//! ```text
//!   blaze-sanitize <capture.ofcap> [-o out.jsonl] [--report report.txt]
//! ```
//!
//! Prints an audit of exactly what was replaced. Nothing is redacted silently:
//! every substitution appears in the report with its path, kind and length, and
//! untouched frames keep their exact wire bytes.
//!
//! The output is a JSONL conversation that replays against either backend —
//! see `blaze-probe --capture`.

use std::io::Write;

use openfut_blaze_host::capture::{self, Direction};
use openfut_blaze_host::sanitize::{self, SENSITIVE_TAGS};
use openfut_protocol_blaze::fire2::Header;

fn usage() -> ! {
    eprintln!("usage: blaze-sanitize <capture.ofcap> [-o out.jsonl] [--report report.txt]");
    eprintln!();
    eprintln!("Reads a raw frame capture and writes a sanitized, replayable JSONL");
    eprintln!("conversation plus an audit of every redaction made.");
    std::process::exit(2);
}

fn arg_after(args: &[String], flag: &str) -> Option<String> {
    args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

fn json_escape(s: &str) -> String {
    s.chars()
        .flat_map(|c| match c {
            '"' => "\\\"".chars().collect::<Vec<_>>(),
            '\\' => "\\\\".chars().collect(),
            '\n' => "\\n".chars().collect(),
            c => vec![c],
        })
        .collect()
}

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    if args.is_empty() || args[0].starts_with('-') {
        usage();
    }
    let input = args[0].clone();
    let out_path = arg_after(&args, "-o").unwrap_or_else(|| format!("{input}.sanitized.jsonl"));
    let report_path = arg_after(&args, "--report");

    let records = match capture::read_file(&input) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("blaze-sanitize: cannot read {input}: {e}");
            std::process::exit(1);
        }
    };
    eprintln!("read {} frame(s) from {input}", records.len());

    let (out, report) = match sanitize::sanitize(&records) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("blaze-sanitize: {e}");
            std::process::exit(1);
        }
    };

    // ------------------------------------------------------------- fixture
    let mut f = match std::fs::File::create(&out_path) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("blaze-sanitize: cannot write {out_path}: {e}");
            std::process::exit(1);
        }
    };

    let _ = writeln!(
        f,
        "{{\"kind\":\"capture\",\"source\":\"{}\",\"frames\":{},\"redacted_frames\":{},\
         \"sensitive_tags\":[{}],\"note\":\"sanitized; redacted values are \
         length-preserving fillers, all other bytes are exact\"}}",
        json_escape(&input),
        report.frames_out,
        report.frames_redacted,
        SENSITIVE_TAGS
            .iter()
            .map(|t| format!("\"{t}\""))
            .collect::<Vec<_>>()
            .join(",")
    );

    for (rec, bytes) in &out {
        let h = Header::parse(bytes).ok();
        let (comp, cmd, mtype, mnum, uidx, plen) = match h {
            Some(h) => (
                h.component,
                h.command,
                h.msg_type.as_bits(),
                h.msg_num,
                h.user_index,
                h.payload_len,
            ),
            None => (0, 0, 0, 0, 0, 0),
        };
        let _ = writeln!(
            f,
            "{{\"kind\":\"frame\",\"conn\":{},\"seq\":{},\"dir\":\"{}\",\
             \"component\":{},\"command\":{},\"msg_type\":{},\"msg_num\":{},\
             \"user_index\":{},\"payload_len\":{},\"frame_hex\":\"{}\"}}",
            rec.conn_id,
            rec.seq,
            rec.dir.label(),
            comp,
            cmd,
            mtype,
            mnum,
            uidx,
            plen,
            hex(bytes)
        );
    }
    eprintln!("wrote {out_path}");

    // -------------------------------------------------------------- report
    let mut lines = Vec::new();
    lines.push(format!("sanitization report for {input}"));
    lines.push(format!("  frames in:       {}", report.frames_in));
    lines.push(format!("  frames out:      {}", report.frames_out));
    lines.push(format!("  frames redacted: {}", report.frames_redacted));
    lines.push(format!("  redactions:      {}", report.redactions.len()));
    lines.push(format!("  sensitive tags:  {}", SENSITIVE_TAGS.join(", ")));
    if !report.undecodable.is_empty() {
        lines.push(format!(
            "  NOT decodable as TDF (passed through unchanged, NOT inspected): seq {:?}",
            report.undecodable
        ));
    }
    lines.push(String::new());
    if report.redactions.is_empty() {
        lines.push("  no sensitive values found".into());
    } else {
        lines.push("  seq   conn  path                 kind    bytes".into());
        for r in &report.redactions {
            lines.push(format!(
                "  {:<5} {:<5} {:<20} {:<7} {} -> {}",
                r.seq, r.conn_id, r.path, r.kind, r.original_len, r.replacement_len
            ));
        }
    }

    // Direction/route summary, so a reviewer can see the conversation shape.
    lines.push(String::new());
    lines.push("  conversation shape:".into());
    let rx = out.iter().filter(|(r, _)| r.dir == Direction::Rx).count();
    let tx = out.len() - rx;
    lines.push(format!("    {rx} RX, {tx} TX"));

    let text = lines.join("\n") + "\n";
    print!("{text}");
    if let Some(p) = report_path {
        if std::fs::write(&p, &text).is_ok() {
            eprintln!("wrote {p}");
        }
    }
}
