// AUDIT VERIFICATION — Finding 13
// AERE402Facilitator replay key is consumed[agentId][nonce] (GLOBAL per agent,
// not per payee). Two providers following the documented "monotonic nonce per
// provider-agent pair" convention collide, and a valid settlement reverts.
// Uses the SAME real deploy stack as test/agentic.test.js. No sources modified.

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

const ONE = 10n ** 18n;

async function deployStack() {
  const [deployer, operator, signer, payeeA, payeeB] = await ethers.getSigners();

  const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
  await WAERE.waitForDeployment();

  const nonce = await ethers.provider.getTransactionCount(deployer.address);
  const futureSaere = ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 2 });
  await WAERE.connect(deployer).deposit({ value: 1000n });
  await WAERE.connect(deployer).approve(futureSaere, 1000n);
  const sAERE = await (await ethers.getContractFactory("sAERE")).deploy(await WAERE.getAddress());
  await sAERE.waitForDeployment();

  const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
  await burnVault.waitForDeployment();
  const fakeRouter = await (await ethers.getContractFactory("WAERE")).deploy();
  await fakeRouter.waitForDeployment();

  const sink = await (await ethers.getContractFactory("AereSink")).deploy(
    await WAERE.getAddress(),
    await burnVault.getAddress(),
    await sAERE.getAddress(),
    await fakeRouter.getAddress(),
    1500, 4000, 4500, 200, ethers.ZeroAddress
  );
  await sink.waitForDeployment();

  const nonce2 = await ethers.provider.getTransactionCount(deployer.address);
  const futureFacilitator = ethers.getCreateAddress({ from: deployer.address, nonce: nonce2 + 1 });

  const agent = await (await ethers.getContractFactory("AereAgent")).deploy(futureFacilitator);
  await agent.waitForDeployment();

  const fac = await (await ethers.getContractFactory("AERE402Facilitator")).deploy(
    await agent.getAddress(),
    await sink.getAddress()
  );
  await fac.waitForDeployment();
  expect((await fac.getAddress()).toLowerCase()).to.equal(futureFacilitator.toLowerCase());

  return { deployer, operator, signer, payeeA, payeeB, WAERE, sink, agent, fac };
}

async function signAuth(signerWallet, fac, agentId, token, payee, amount, resourceId, nonce, deadline) {
  const domain = {
    name: "AERE402Facilitator",
    version: "1",
    chainId: (await ethers.provider.getNetwork()).chainId,
    verifyingContract: await fac.getAddress(),
  };
  const types = {
    PaymentAuth: [
      { name: "agentId", type: "uint256" },
      { name: "token", type: "address" },
      { name: "payee", type: "address" },
      { name: "amount", type: "uint256" },
      { name: "resourceId", type: "bytes32" },
      { name: "nonce", type: "uint256" },
      { name: "deadline", type: "uint256" },
    ],
  };
  const value = { agentId, token, payee, amount, resourceId, nonce, deadline };
  return signerWallet.signTypedData(domain, types, value);
}

describe("AUDIT VERIFY — F13 AERE402Facilitator per-agent (not per-payee) nonce collision", function () {
  it("CONFIRMED: two providers using the same monotonic nonce collide; provider B's valid settlement reverts", async () => {
    const { operator, signer, payeeA, payeeB, WAERE, agent, fac } = await deployStack();

    const tx = await agent.connect(operator).registerAgent(
      signer.address, ethers.ZeroHash, ethers.ZeroHash, 10n * ONE, 0, 0, ""
    );
    const agentId = (await tx.wait()).logs.find(l => l.fragment?.name === "AgentRegistered").args.agentId;

    // Fund the agent generously so balance is NOT the limiting factor.
    await WAERE.connect(operator).deposit({ value: 100n * ONE });
    await WAERE.connect(operator).approve(await agent.getAddress(), 100n * ONE);
    await agent.connect(operator).topUp(agentId, await WAERE.getAddress(), 100n * ONE);

    const amount = 2n * ONE;
    const deadline = (await ethers.provider.getBlock("latest")).timestamp + 3600;
    const token = await WAERE.getAddress();

    // Provider A: agentId, nonce=1, payee = payeeA. (A's first invoice.)
    const sigA = await signAuth(signer, fac, agentId, token, payeeA.address, amount, ethers.id("providerA:/gpu"), 1n, deadline);
    await fac.connect(payeeA).settle(agentId, token, payeeA.address, amount, ethers.id("providerA:/gpu"), 1n, deadline, sigA);
    expect(await fac.consumed(agentId, 1n)).to.equal(true);

    // Provider B: a DIFFERENT, fully valid payment to payeeB, but with B's own
    // monotonic nonce=1 (per the documented per-provider convention). The agent
    // signed it and has ample balance — yet it reverts because the nonce key is
    // global per agent, so A already consumed (agentId, 1).
    const sigB = await signAuth(signer, fac, agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 1n, deadline);
    await expect(
      fac.connect(payeeB).settle(agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 1n, deadline, sigB)
    ).to.be.revertedWithCustomError(fac, "NonceAlreadyConsumed");

    // Control: provider B succeeds only by picking a non-colliding nonce (proving
    // it was the shared nonce namespace, not signature/balance, that blocked it).
    const sigB2 = await signAuth(signer, fac, agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 2n, deadline);
    await fac.connect(payeeB).settle(agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 2n, deadline, sigB2);
    expect(await fac.consumed(agentId, 2n)).to.equal(true);
  });
});
