const { expect } = require("chai");
const { ethers } = require("hardhat");

// ---------------------------------------------------------------------------
// AereCryptoRegistry: canonical scheme-ID constants (crypto-agility hardening).
//
// Covers the PURELY ADDITIVE SCHEME_* constants added per
// docs/PQ-AGILITY-HARDENING-2026-07-18.md:
//   - every constant resolves to its canonical number (ids 1..14),
//   - the genesis constants (1..5) equal the ids seedLiveSchemes assigns,
//   - all 14 ids are distinct,
//   - a RESERVED id (ML-DSA-65) registered against a verifier resolves end to end
//     via precompileFor / resolveActive / isUsable,
//   - an ML-KEM discovery entry (isSignature=false) is refused by the signature-only
//     verify() path.
//
// These constants are reference values only: ids 6..14 name reserved parameter sets and
// do NOT seed, activate, or route anything until governance adds a row. No precompile mock
// is needed here (no cryptographic verify is exercised; the reserved verifier is a
// placeholder address used only for routing/lookup).
// ---------------------------------------------------------------------------

const EXPECTED = {
  // genesis (live on chain 2800)
  SCHEME_FALCON512: 1,
  SCHEME_FALCON1024: 2,
  SCHEME_MLDSA44: 3,
  SCHEME_SLHDSA128S: 4,
  SCHEME_SHAKE256: 5,
  // reserved future schemes (no live verifier yet)
  SCHEME_MLDSA65: 6,
  SCHEME_MLDSA87: 7,
  SCHEME_FALCON512_PADDED: 8,
  SCHEME_FALCON1024_PADDED: 9,
  SCHEME_SLHDSA192S: 10,
  SCHEME_SLHDSA256S: 11,
  SCHEME_MLKEM512: 12,
  SCHEME_MLKEM768: 13,
  SCHEME_MLKEM1024: 14,
};

// The five live precompile addresses seedLiveSchemes wires (scheme => verifier).
const LIVE_PRECOMPILE = {
  1: ethers.getAddress("0x0000000000000000000000000000000000000ae1"),
  2: ethers.getAddress("0x0000000000000000000000000000000000000ae2"),
  3: ethers.getAddress("0x0000000000000000000000000000000000000ae3"),
  4: ethers.getAddress("0x0000000000000000000000000000000000000ae4"),
};

// A stand-in verifier for a RESERVED scheme. Nonzero (addAlgorithm requires it) but never
// staticcalled by this test: only registration/lookup/resolution are exercised, not verify.
const PLACEHOLDER_VERIFIER = ethers.getAddress("0x00000000000000000000000000000000000c0de1");

const Status = { UNKNOWN: 0, ACTIVE: 1, DEPRECATED: 2, REVOKED: 3 };

describe("AereCryptoRegistry: canonical scheme-ID constants", function () {
  let reg;

  beforeEach(async function () {
    const R = await ethers.getContractFactory("AereCryptoRegistry");
    reg = await R.deploy();
    await reg.waitForDeployment();
  });

  it("every SCHEME_* constant resolves to its canonical number", async function () {
    for (const [name, value] of Object.entries(EXPECTED)) {
      const got = Number(await reg[name]());
      expect(got, name).to.equal(value);
    }
  });

  it("assigns 14 distinct ids across the whole canonical space", async function () {
    const seen = new Set();
    for (const name of Object.keys(EXPECTED)) {
      const v = Number(await reg[name]());
      expect(seen.has(v), `duplicate id ${v} at ${name}`).to.equal(false);
      seen.add(v);
    }
    expect(seen.size).to.equal(14);
  });

  it("genesis constants equal the scheme ids seedLiveSchemes assigns", async function () {
    await reg.seedLiveSchemes();
    // Row id == scheme number for the seeded set; assert the row for each genesis constant
    // carries that same scheme tag and (for signature schemes) routes to the live precompile.
    const genesis = [
      { c: "SCHEME_FALCON512", id: 1, live: true },
      { c: "SCHEME_FALCON1024", id: 2, live: true },
      { c: "SCHEME_MLDSA44", id: 3, live: true },
      { c: "SCHEME_SLHDSA128S", id: 4, live: true },
      { c: "SCHEME_SHAKE256", id: 5, live: false }, // hash-only, not a routable signature
    ];
    for (const g of genesis) {
      const scheme = Number(await reg[g.c]());
      expect(scheme).to.equal(g.id);
      const a = await reg.getAlgorithm(g.id);
      expect(Number(a.scheme), g.c).to.equal(scheme);
      if (g.live) {
        expect(await reg.precompileFor(scheme)).to.equal(LIVE_PRECOMPILE[g.id]);
        expect(await reg.isUsable(g.id)).to.equal(true);
      } else {
        // SHAKE256 is ACTIVE but hash-only: not usable as a signature verifier.
        expect(await reg.isActive(g.id)).to.equal(true);
        expect(await reg.isUsable(g.id)).to.equal(false);
      }
    }
  });

  it("a RESERVED signature id (ML-DSA-65) resolves end to end once a verifier is registered", async function () {
    await reg.seedLiveSchemes();
    const scheme = Number(await reg.SCHEME_MLDSA65()); // 6

    // Real FIPS 204 ML-DSA-65 lengths; fixed-concatenation family (esigHeader 0).
    const ML_DSA_65_PK = 1952;
    const ML_DSA_65_SIG = 3309;
    const tx = await reg.addAlgorithm(
      "ML-DSA-65", scheme, PLACEHOLDER_VERIFIER, ML_DSA_65_PK, ML_DSA_65_SIG, 0, 380000, true
    );
    const rc = await tx.wait();
    const log = rc.logs.map((l) => reg.interface.parseLog(l)).find((p) => p && p.name === "AlgorithmAdded");
    const id = Number(log.args.id);
    expect(id).to.equal(6); // first row after the 5 seeded

    // The reserved id now resolves through the registry's routing surface.
    const a = await reg.getAlgorithm(id);
    expect(Number(a.scheme)).to.equal(scheme);
    expect(a.verifier).to.equal(PLACEHOLDER_VERIFIER);
    expect(Number(a.pubKeyLen)).to.equal(ML_DSA_65_PK);
    expect(Number(a.sigLen)).to.equal(ML_DSA_65_SIG);
    expect(a.isSignature).to.equal(true);
    expect(Number(a.status)).to.equal(Status.ACTIVE);

    expect(await reg.precompileFor(scheme)).to.equal(PLACEHOLDER_VERIFIER);
    expect(Number(await reg.resolveActive(id))).to.equal(id); // ACTIVE resolves to itself
    expect(await reg.isUsable(id)).to.equal(true);
  });

  it("an ML-KEM discovery entry (isSignature=false) is refused by the signature-only verify()", async function () {
    await reg.seedLiveSchemes();
    const scheme = Number(await reg.SCHEME_MLKEM512()); // 12

    // ML-KEM-512 (FIPS 203): encapsulation key 800 bytes; registered as a NON-signature
    // discovery entry, so verify() must refuse to route it (fail-closed, no revert).
    const ML_KEM_512_EK = 800;
    const tx = await reg.addAlgorithm(
      "ML-KEM-512", scheme, PLACEHOLDER_VERIFIER, ML_KEM_512_EK, 0, 0, 40000, false
    );
    const rc = await tx.wait();
    const log = rc.logs.map((l) => reg.interface.parseLog(l)).find((p) => p && p.name === "AlgorithmAdded");
    const id = Number(log.args.id);

    const a = await reg.getAlgorithm(id);
    expect(Number(a.scheme)).to.equal(scheme);
    expect(a.isSignature).to.equal(false);
    // Discovery entry: ACTIVE (discoverable) but never a usable signature verifier.
    expect(await reg.isActive(id)).to.equal(true);
    expect(await reg.isUsable(id)).to.equal(false);

    // verify() returns false for a non-signature row without ever touching a verifier.
    const anyPk = "0x" + "00".repeat(ML_KEM_512_EK);
    const anyMsg = ethers.keccak256(ethers.toUtf8Bytes("kem"));
    const anySig = "0x" + "11".repeat(64);
    expect(await reg.verify(id, anyPk, anyMsg, anySig)).to.equal(false);
  });
});
