//! Conformance corpus for typed receipt classes and the soft binding class
//! (`claim-conformance/`, extensions/jwp-receipts.md §2.4.3–§2.4.9, §2.6, §3.1–§3.2).
//!
//! Every vector declares one outcome: `accept` (with the claim's link hash, which pins
//! the canonical form; or, for a recovery, the ancestor it attests), or `reject` at a
//! named §3.1 step with a named reason. A verifier that rejects for a different reason,
//! or at a different step, does not conform.
//!
//!   wai_claim_vectors gen    <corpus-dir>   write vectors.json
//!   wai_claim_vectors verify <corpus-dir>   the canonical fixture holds, the pinned file
//!                                           regenerates byte-identically, and every
//!                                           vector gets its declared outcome
//!   wai_claim_vectors verdicts <file>       one `name<TAB>verdict` line per vector of a
//!                                           corpus-format file, any declared outcome
//!                                           ignored — the input to
//!                                           `claim-conformance/differential.py`
//!
//! The generator and this verifier are one implementation, so the corpus alone cannot
//! show that this one reads the specification correctly. Four things are checked
//! against code that shares nothing with it: the canonical fixture (bytes, signature and
//! link hash computed by `claim-conformance/claim_verify.py`); the bracket arithmetic of
//! energy-measurement §6.2 (figures and least declarations that script computes with
//! Python's integers); and — in CI, on the linux runner — that script's own verification
//! of every vector, and `differential.py`, which compares the two verifiers' verdicts on
//! generated inputs the corpus does not hold.

use std::path::Path;
use wai::claim_receipt::{check_canonical_form, conformance_vectors, corpus_verdicts, corpus_vector_count, run_corpus};

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, conformance_vectors())) {
                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_claim_vectors gen|verify <corpus-dir> | verdicts <file>");
            std::process::exit(2);
        }
    }
}

/// Print `name<TAB>verdict` for every vector in a corpus-format file, ignoring any
/// declared outcome — the input to a differential comparison with another verifier.
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;
        }
    };
    match corpus_verdicts(&doc) {
        Ok(vs) => {
            for (name, got) in vs {
                println!("{name}\t{got}");
            }
            0
        }
        Err(e) => {
            eprintln!("{}: {e}", file.display());
            1
        }
    }
}

fn verify(dir: &Path) -> i32 {
    let mut fail = 0usize;

    let cf = check_canonical_form();
    println!(
        "{} canonical form pinned to its independently computed fixture  got={}",
        if cf.is_empty() { "PASS" } else { "FAIL" },
        cf.join(",")
    );
    fail += (!cf.is_empty()) as usize;

    let ba = wai::energy_report::check_bracket_arithmetic();
    println!(
        "{} bracket arithmetic pinned to its independently computed values  got={}",
        if ba.is_empty() { "PASS" } else { "FAIL" },
        ba.join(",")
    );
    fail += (!ba.is_empty()) as usize;

    let path = dir.join("vectors.json");
    // `.gitattributes` pins LF; a checkout that ignores it (a CRLF working tree) changes
    // only the pretty-print line breaks, never a receipt, so it is undone rather than
    // reported as a corpus change.
    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 == conformance_vectors();
    println!("{} pinned corpus regenerates byte-identically", if same { "PASS" } else { "FAIL" });
    fail += (!same) as usize;

    match run_corpus(&pinned) {
        Ok(results) => {
            if results.len() != corpus_vector_count() {
                println!("FAIL {}: {} vectors, expected {}", path.display(), results.len(), corpus_vector_count());
                fail += 1;
            }
            for r in results {
                println!(
                    "{} {}  expected={}  got={}",
                    if r.pass { "PASS" } else { "FAIL" },
                    r.name,
                    r.expected,
                    r.got
                );
                fail += (!r.pass) 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
    }
}
