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

// ---------------------------------------------------------------------------
// AerePQStarkVerifier - ISP1Verifier-compatible adapter for the native PQ STARK
// precompile at 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify, no BN254 wrap).
//
// HONEST SCOPE: the precompile at 0x0AE8 is a REFERENCE SKELETON that fail-closes
// (returns EMPTY for every input) and is NOT deployed on the hardhat EVM at all. So
// on this test EVM a staticcall to 0x0AE8 hits an empty account and returns empty ->
// the adapter must REVERT on verifyProof and return false on isValidProof for every
// input. That is exactly the safety property we want to prove: a consumer swapped to
// this adapter DENIES all proofs (never a false accept) until the precompile core is
// ported, externally audited, and founder-activated.
//
// This test also demonstrates the ONE-LINE consumer swap: it deploys the REAL
// AereEVMValidityV2 consumer with SP1_VERIFIER pointed at the PQ adapter (instead of
// the BN254 gateway) and shows the consumer fail-closes end-to-end.
// ---------------------------------------------------------------------------

const PQ_STARK_ADDR = ethers.getAddress("0x0000000000000000000000000000000000000ae8");
const SUCCESS_WORD = "0x" + "00".repeat(31) + "01";

describe("AerePQStarkVerifier (PQ STARK precompile adapter @ 0x0AE8)", function () {
  let adapter;

  beforeEach(async function () {
    const F = await ethers.getContractFactory("AerePQStarkVerifier");
    adapter = await F.deploy(1); // configId 1 = SP1 inner/shrink
    await adapter.waitForDeployment();
  });

  it("targets precompile 0x0AE8 and reports its config/version", async function () {
    expect(await adapter.CONFIG_ID()).to.equal(1);
    expect(await adapter.VERSION()).to.equal("pq-stark-v1(reference-skeleton)");
    // domain-separated tag, distinct from any BN254 SP1 VERIFIER_HASH
    expect(await adapter.VERIFIER_HASH()).to.equal(
      ethers.keccak256(ethers.toUtf8Bytes("AerePQStarkVerifier.v1"))
    );
  });

  it("rejects configId outside {1,2}", async function () {
    const F = await ethers.getContractFactory("AerePQStarkVerifier");
    await expect(F.deploy(0)).to.be.reverted;
    await expect(F.deploy(3)).to.be.reverted;
    const two = await F.deploy(2);
    await two.waitForDeployment();
    expect(await two.CONFIG_ID()).to.equal(2);
  });

  it("FAIL-CLOSED: verifyProof reverts when the precompile is absent/empty (no false accept)", async function () {
    const vkey = ethers.hexlify(ethers.randomBytes(32));
    const publicValues = ethers.hexlify(ethers.randomBytes(96));
    const proof = ethers.hexlify(ethers.randomBytes(512));
    await expect(
      adapter.verifyProof(vkey, publicValues, proof)
    ).to.be.revertedWithCustomError(adapter, "PQProofInvalid");
  });

  it("FAIL-CLOSED: verifyProof reverts even on empty public values / empty proof", async function () {
    const vkey = ethers.ZeroHash;
    await expect(
      adapter.verifyProof(vkey, "0x", "0x")
    ).to.be.revertedWithCustomError(adapter, "PQProofInvalid");
  });

  it("FAIL-CLOSED: isValidProof returns false (never true) for arbitrary input", async function () {
    const vkey = ethers.hexlify(ethers.randomBytes(32));
    const publicValues = ethers.hexlify(ethers.randomBytes(288));
    const proof = ethers.hexlify(ethers.randomBytes(1024));
    expect(await adapter.isValidProof(vkey, publicValues, proof)).to.equal(false);
  });

  it("would ACCEPT only on the exact 32-byte success word (mocked precompile via setCode)", async function () {
    // Prove the accept path is real: inject a tiny mock at 0x0AE8 that returns 0x..01,
    // then the SAME adapter call succeeds. This shows the adapter is a faithful gate -
    // it is the *precompile skeleton*, not the adapter, that currently denies everything.
    // A contract that unconditionally returns the 32-byte success word:
    //   PUSH1 0x01 PUSH1 0x00 MSTORE  (mem[0..32] = 0x..01)
    //   PUSH1 0x20 PUSH1 0x00 RETURN
    const runtime = "0x600160005260206000f3";
    await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, runtime]);
    try {
      const vkey = ethers.hexlify(ethers.randomBytes(32));
      // staticcall to a mock returns success word -> verifyProof does NOT revert
      await adapter.verifyProof.staticCall(vkey, "0x", "0x");
      expect(await adapter.isValidProof(vkey, "0x", "0x")).to.equal(true);
    } finally {
      await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, "0x"]);
    }
  });

  it("rejects a mock precompile that returns the WRONG word (not 0x..01)", async function () {
    // mem[0..32] = 0x..02 then return 32 bytes
    const runtime = "0x600260005260206000f3";
    await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, runtime]);
    try {
      const vkey = ethers.hexlify(ethers.randomBytes(32));
      expect(await adapter.isValidProof(vkey, "0x", "0x")).to.equal(false);
    } finally {
      await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, "0x"]);
    }
  });

  // -------------------------------------------------------------------------
  // ONE-LINE CONSUMER SWAP: AereEVMValidityV2 with SP1_VERIFIER = PQ adapter.
  // -------------------------------------------------------------------------
  describe("one-line swap into a real consumer (AereEVMValidityV2)", function () {
    it("deploys AereEVMValidityV2 against the PQ adapter and fail-closes end-to-end", async function () {
      const vkey = ethers.hexlify(ethers.randomBytes(32));
      const V = await ethers.getContractFactory("AereEVMValidityV2");
      // THE SWAP: first constructor arg is SP1_VERIFIER. Passing the PQ adapter instead
      // of the BN254 gateway 0x9ca479... is the entire change. No consumer code edited.
      const consumer = await V.deploy(await adapter.getAddress(), vkey);
      await consumer.waitForDeployment();

      expect(await consumer.SP1_VERIFIER()).to.equal(await adapter.getAddress());
      expect(await consumer.PROGRAM_VKEY()).to.equal(vkey);

      // 288-byte public values (9 x 32) the V2 consumer expects.
      const abi = ethers.AbiCoder.defaultAbiCoder();
      const publicValues = abi.encode(
        ["uint256", "uint256", "bytes32", "bytes32", "bytes32", "bytes32", "bytes32", "uint256", "uint256"],
        [2800, 12345, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, 0, 0]
      );
      const proof = ethers.hexlify(ethers.randomBytes(512));

      // Precompile absent -> adapter reverts -> consumer.verify() reverts. Fail-closed.
      await expect(consumer.verify(publicValues, proof)).to.be.reverted;
    });
  });
});
