//! Conformance corpus for key discovery, signing authority and module authorisation
//! (`signer-conformance/`, extensions/jwp-receipts.md §7, §5.8).
//!
//! Every vector declares one verdict: for `confirm`, `confirmed` with the origin, key-id,
//! head and anchor path, or `unconfirmed`/`refused` at a named §7.4 step with a named
//! reason, or `reject` for a claim that fails §3.1; for `anchor`, `anchored`,
//! `not_anchored` with a reason, or `replaced`; for `module`, `run` with the kill latency,
//! or `fallback`/`inert` at a named §7.9 check. A verifier that reaches another verdict
//! does not conform.
//!
//!   wai_signer_vectors gen    <corpus-dir>   write vectors.json
//!   wai_signer_vectors verify <corpus-dir>   the canonical fixture holds, the pinned file
//!                                            regenerates byte-identically, and every
//!                                            vector gets its declared verdict
//!   wai_signer_vectors verdicts <file>       one `name<TAB>verdict` line per vector,
//!                                            any declared verdict ignored
//!
//! The generator and this verifier are one implementation. The canonical fixture's bytes,
//! signature and revision hash were computed by `signer-conformance/signer_verify.py`,
//! which shares no code with it and also reports every vector's verdict from the text
//! of §7. CI runs that script and compares its `verdicts` output with this binary's.

use std::path::Path;
use wai::key_log::{check_key_log_canonical_form as 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_signer_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 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 signed text, 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
    }
}
