//! Conformance corpus for labelled leaves, labelled group receipts, the group
//! total and the `WAI_RECEIPT` property (`jwp-conformance/`, extensions/jwp-receipts.md
//! §2.2–§2.4.2, §3, §3.3 and §5.7, moq-streaming-format §5).
//!
//! Every vector declares one outcome: a leaf hash; `accept` (for a group, with its
//! link hash and signing payload); a reading of a total or a property; or a
//! rejection with its reason, and its §3 step where it has one.
//!
//!   wai_jwp_vectors gen    <corpus-dir>   write vectors.json
//!   wai_jwp_vectors verify <corpus-dir>   the pinned file regenerates byte-identically,
//!                                         and every vector gets its declared outcome
//!   wai_jwp_vectors verdicts <file>       one `name<TAB>verdict` line per vector,
//!                                         any declared outcome ignored
//!
//! `jwp-conformance/jwp_verify.py`, written from the extension text and sharing no
//! code with this crate, reports the same outcomes; CI runs it.

use std::path::Path;
use wai::jwp_delivery::corpus::{generate, run, vector_count, verdict};

fn main() {
    let a: Vec<String> = std::env::args().collect();
    match (a.get(1).map(String::as_str), a.get(2)) {
        (Some("gen"), Some(d)) => {
            let p = Path::new(d).join("vectors.json");
            if let Err(e) = std::fs::create_dir_all(d).and_then(|_| std::fs::write(&p, generate())) {
                eprintln!("write {}: {e}", p.display());
                std::process::exit(1);
            }
            println!("wrote {}", p.display());
        }
        (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))),
        (Some("verdicts"), Some(f)) => std::process::exit(verdicts(Path::new(f))),
        _ => {
            eprintln!("usage: wai_jwp_vectors gen|verify <corpus-dir> | verdicts <file>");
            std::process::exit(2);
        }
    }
}

fn verdicts(file: &Path) -> i32 {
    let doc = match std::fs::read_to_string(file) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("read {}: {e}", file.display());
            return 1;
        }
    };
    let Ok(d) = serde_json::from_str::<serde_json::Value>(&doc) else {
        eprintln!("{}: not JSON", file.display());
        return 1;
    };
    for v in d.get("vectors").and_then(|v| v.as_array()).into_iter().flatten() {
        let name = v.get("name").and_then(|n| n.as_str()).unwrap_or("?");
        println!("{name}\t{}", verdict(v).unwrap_or_else(|_| "error".into()));
    }
    0
}

fn verify(dir: &Path) -> i32 {
    let mut fail = 0usize;
    let path = dir.join("vectors.json");
    // `.gitattributes` pins LF; a CRLF working tree changes only line breaks.
    let pinned = match std::fs::read_to_string(&path) {
        Ok(s) => s.replace("\r\n", "\n"),
        Err(e) => {
            println!("FAIL {}: {e}", path.display());
            return 1;
        }
    };
    let same = pinned == generate();
    println!("{} pinned corpus regenerates byte-identically", if same { "PASS" } else { "FAIL" });
    fail += (!same) as usize;
    match run(&pinned) {
        Ok(results) => {
            if results.len() != vector_count() {
                println!("FAIL {}: {} vectors, expected {}", path.display(), results.len(), vector_count());
                fail += 1;
            }
            for (name, want, got) in results {
                let ok = want == got;
                println!("{} {name}  expected={want}  got={got}", if ok { "PASS" } else { "FAIL" });
                fail += (!ok) as usize;
            }
        }
        Err(e) => {
            println!("FAIL {}: {e}", path.display());
            fail += 1;
        }
    }
    if fail == 0 {
        println!("all vectors pass");
        0
    } else {
        println!("{fail} failure(s)");
        1
    }
}
