/**
 * verify-mldsa65-engine.ts — proves the REAL ML-DSA-65 KAT holds THROUGH the
 * MLDSAEngine public handlers (server/pqcSignatureEngine.ts), not just raw @noble.
 *
 * Companion to verify-mlkem768-engine.ts (ML-KEM-768). Legs:
 *   [A] keyGen byte-exact vs NIST — engine.generateKeyPairFromSeed(seed) reproduces the NIST
 *       pk + sk for ALL ML-DSA-65 keyGen AFT vectors (deterministic keygen from xi).
 *   [B] sigGen byte-exact vs NIST — for the deterministic / external / pure sigGen group (the
 *       path the engine implements), engine.signWith(sk, msg, {deterministic, context}) reproduces
 *       the NIST signature byte-exact for every vector.
 *   [C] verify-positive through the engine — engine.verifyWith(pk, msg, NIST sig, context) === true.
 *   [D] paired negative controls so a green result cannot be hollow — tamper message, tamper
 *       signature, and wrong public key each verify === false.
 *
 * Vectors: official NIST ACVP FIPS-204 internalProjection, pinned + SHA-256 vendored in
 * tests/fixtures/mldsa65/ (see PROVENANCE.md). Vendored verbatim (all parameter sets); this
 * verifier filters to parameterSet === "ML-DSA-65" at runtime.
 *
 * Rule 1: never print key / secret / signature bytes — only tcIds, lengths, booleans.
 * Exit 0 iff every leg green.
 *
 * Run: npx tsx scripts/verify-mldsa65-engine.ts
 */
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { MLDSAEngine, ML_DSA_65 } from "../server/pqcSignatureEngine";

const HERE = path.dirname(fileURLToPath(import.meta.url));
const FIX = path.resolve(HERE, "../tests/fixtures/mldsa65");
const PARAM = "ML-DSA-65";

function hexToBuf(hex: string): Buffer {
  return Buffer.from(hex, "hex");
}
function eqBytes(a: Buffer, b: Buffer): boolean {
  return a.length === b.length && a.equals(b);
}

let passCount = 0,
  failCount = 0;
function ok(cond: boolean, label: string): boolean {
  if (cond) passCount++;
  else {
    failCount++;
    console.log(`  ✗ FAIL: ${label}`);
  }
  return cond;
}

function load(name: string): any {
  return JSON.parse(readFileSync(path.join(FIX, name), "utf8"));
}

function main(): void {
  const engine = new MLDSAEngine();

  // ── [A] keyGen byte-exact through engine.generateKeyPairFromSeed ─────────────
  const kg = load("ML-DSA-keyGen-FIPS204.internalProjection.json");
  const kgGroups = kg.testGroups.filter((g: any) => g.parameterSet === PARAM);
  let kgTests = 0;
  for (const g of kgGroups) {
    for (const t of g.tests) {
      kgTests++;
      const seed = hexToBuf(t.seed);
      const kp = engine.generateKeyPairFromSeed(seed);
      ok(kp.publicKey.length === ML_DSA_65.publicKeySize, `[A] tcId ${t.tcId} pk length`);
      ok(kp.secretKey.length === ML_DSA_65.secretKeySize, `[A] tcId ${t.tcId} sk length`);
      ok(eqBytes(kp.publicKey, hexToBuf(t.pk)), `[A] tcId ${t.tcId} pk byte-exact vs NIST`);
      ok(eqBytes(kp.secretKey, hexToBuf(t.sk)), `[A] tcId ${t.tcId} sk byte-exact vs NIST`);
    }
  }
  console.log(`  [A] keyGen: ${kgTests} ML-DSA-65 vectors checked`);

  // ── [B]/[C]/[D] sigGen deterministic/external/pure ───────────────────────────
  const sg = load("ML-DSA-sigGen-FIPS204.internalProjection.json");
  const sgGroups = sg.testGroups.filter(
    (g: any) =>
      g.parameterSet === PARAM &&
      g.deterministic === true &&
      g.signatureInterface === "external" &&
      g.preHash === "pure",
  );
  ok(sgGroups.length > 0, "[B] found a deterministic/external/pure ML-DSA-65 sigGen group");
  let sgTests = 0;
  for (const g of sgGroups) {
    for (const t of g.tests) {
      sgTests++;
      const sk = hexToBuf(t.sk);
      const pk = hexToBuf(t.pk);
      const msg = hexToBuf(t.message);
      const ctx = t.context ? hexToBuf(t.context) : Buffer.alloc(0);
      const nistSig = hexToBuf(t.signature);

      // [B] sign byte-exact vs NIST
      const sig = engine.signWith(sk, msg, { deterministic: true, context: ctx });
      ok(sig.length === ML_DSA_65.signatureSize, `[B] tcId ${t.tcId} signature length`);
      ok(eqBytes(sig, nistSig), `[B] tcId ${t.tcId} signature byte-exact vs NIST`);

      // [C] verify-positive on the NIST signature through the engine
      ok(engine.verifyWith(pk, msg, nistSig, ctx) === true, `[C] tcId ${t.tcId} NIST sig verifies true`);

      // [D] negative controls (only on the first few to keep runtime bounded)
      if (sgTests <= 5) {
        const badMsg = Buffer.from(msg);
        badMsg[badMsg.length - 1] ^= 0x01;
        ok(engine.verifyWith(pk, badMsg, nistSig, ctx) === false, `[D] tcId ${t.tcId} tampered message rejected`);
        const badSig = Buffer.from(nistSig);
        badSig[50] ^= 0xff;
        ok(engine.verifyWith(pk, msg, badSig, ctx) === false, `[D] tcId ${t.tcId} tampered signature rejected`);
        const otherPk = Buffer.from(engine.generateKeyPair().publicKey);
        ok(engine.verifyWith(otherPk, msg, nistSig, ctx) === false, `[D] tcId ${t.tcId} wrong public key rejected`);
      }
    }
  }
  console.log(`  [B/C/D] sigGen: ${sgTests} ML-DSA-65 deterministic/external/pure vectors checked`);

  // ── engine self-test (dependency-free liveness/tamper gate) ──────────────────
  const st = engine.selfTest();
  for (const c of st.checks) ok(c.ok, `[selfTest] ${c.name}`);

  console.log("");
  console.log(`  RESULT: ${passCount} passed, ${failCount} failed`);
  if (failCount > 0) {
    console.log("  ✗ ML-DSA-65 KAT FAILED");
    process.exit(1);
  }
  console.log("  ✓ ML-DSA-65 KAT GREEN — byte-exact vs NIST ACVP FIPS-204 through the engine");
  process.exit(0);
}

main();
