//! The result record of a `wai conform` run, `conform-result/1`
//! (`extensions/conformance-results.md`): its members (§2), its signature (§3) and its
//! verification (§4) are the library's
//! (`wai::conform_result`); this module adds the run's key and how `wai verify` reports a
//! record.

use ed25519_dalek::SigningKey;
use serde_json::{Map, Value};
use wai::conform_result;

pub use wai::conform_result::FORMAT;

/// The suffix of a result record's file name, which `wai verify` looks for.
pub const SUFFIX: &str = ".wai-conform.json";

/// A signing key from `--key`: its name in the keystore, or a key file (64 hexadecimal digits, a
/// 32-byte seed, or the 32 bytes themselves).
pub fn key_from_file(path: &std::path::Path) -> Result<SigningKey, String> {
    super::super::key::signing_key(&path.to_string_lossy(), false).map_err(|e| e.message)
}

/// A key made for one run.
pub fn ephemeral_key() -> Result<SigningKey, String> {
    let mut seed = [0u8; 32];
    getrandom::fill(&mut seed).map_err(|e| format!("no randomness for a run key: {e}"))?;
    Ok(SigningKey::from_bytes(&seed))
}

/// `record` signed with `key` (§3).
pub fn sign(record: Map<String, Value>, key: &SigningKey) -> Result<Map<String, Value>, String> {
    conform_result::sign(record, key)
}

/// A result record checked (§4), then held to §4.1 as a relying party that admits an
/// implementation on it: the run passed (rule 2), its kind and suite are one this build runs
/// and whose corpus `root` (a checkout's `wai/` directory) holds (rule 3), and its corpus
/// digest is that corpus's (rule 4). Whether it is admitted, and a line for each thing
/// checked.
pub fn admit(bytes: &[u8], root: &std::path::Path) -> (bool, Vec<String>) {
    let (_, mut lines) = check(bytes);
    let Ok(a) = conform_result::check(bytes) else {
        return (false, lines);
    };
    let mut refuse = |rule: u8, code: &str, what: String| {
        lines.push(format!("FAIL admission rule {rule} {code}: {what}"));
        (false, std::mem::take(&mut lines))
    };
    if !a.conformant {
        return refuse(2, "not_conformant", format!("the run it records did not pass ({} of {} cases failed)", a.cases - a.passed, a.cases));
    }
    let Some(suite) = super::suites::SUITES.iter().find(|s| s.kind == a.kind && s.name == a.suite) else {
        return refuse(3, "suite_unknown", format!("{} {} is no suite this build runs", a.kind, a.suite));
    };
    let files = match (suite.load)(&root.join(suite.dir)) {
        Ok(l) => l.files,
        Err(e) => return refuse(3, "suite_unknown", format!("the corpus of {} {} does not read here: {e}", a.kind, a.suite)),
    };
    let here = match super::corpus_digest(root, &files) {
        Ok(d) => d,
        Err(e) => return refuse(3, "suite_unknown", e.message),
    };
    if here != a.corpus_digest {
        return refuse(4, "corpus_mismatch", format!("it names the corpus {}, and the corpus of {} {} here is {here}", a.corpus_digest, a.kind, a.suite));
    }
    lines.push(format!("ok   admitted: {} {} passed against the corpus of {}", a.kind, a.suite, root.display()));
    (true, lines)
}

/// A result record checked (§4): whether it holds, and a line for each thing checked.
pub fn check(bytes: &[u8]) -> (bool, Vec<String>) {
    match conform_result::check(bytes) {
        Err(r) => {
            let what = match r.reason {
                conform_result::Reason::SignatureInvalid => "the signature does not verify: the record is not the one that was signed".to_string(),
                conform_result::Reason::SummaryMismatch => format!("the summary does not count the cases ({})", r.detail),
                _ => r.detail.clone(),
            };
            (false, vec![format!("FAIL step {} {}: {what}", r.step, r.reason.code())])
        }
        Ok(a) => (
            true,
            vec![
                format!("ok   signed by {}… ({} key)", &a.signer_pubkey[..16], a.key),
                format!(
                    "ok   {} {} against {}: {} of {} cases passed, {}; corpus {}; WAI {}",
                    a.kind,
                    a.suite,
                    a.implementation,
                    a.passed,
                    a.cases,
                    if a.conformant { "conformant" } else { "not conformant" },
                    a.corpus_digest,
                    a.standard
                ),
            ],
        ),
    }
}
