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

describe("AereFaucetV2 — anonymous claim", () => {
  let owner, signer, recipient, outsider;
  let f;

  beforeEach(async () => {
    [owner, signer, recipient, outsider] = await ethers.getSigners();
    const F = await ethers.getContractFactory("AereFaucetV2");
    f = await F.deploy(signer.address);
    await f.waitForDeployment();
    // Seed 10 AERE.
    await owner.sendTransaction({ to: await f.getAddress(), value: ethers.parseEther("10") });
  });

  async function ticket(commitment, recipientAddr, validUntil) {
    const inner = ethers.AbiCoder.defaultAbiCoder().encode(
      ["string", "uint256", "address", "bytes32", "address", "uint64"],
      ["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commitment, recipientAddr, validUntil]
    );
    const innerHash = ethers.keccak256(inner);
    return signer.signMessage(ethers.getBytes(innerHash));
  }

  it("regular claim works with cooldown", async () => {
    const before = await ethers.provider.getBalance(outsider.address);
    await f.connect(outsider).claim();
    const after = await ethers.provider.getBalance(outsider.address);
    expect(after).to.be.gt(before); // roughly + dripAmount - gas
    await expect(f.connect(outsider).claim()).to.be.revertedWithCustomError(f, "InCooldown");
  });

  it("anonymous claim with valid ticket sends drip to recipient", async () => {
    const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-1"));
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
    const sig = await ticket(commit, recipient.address, validUntil);

    const before = await ethers.provider.getBalance(recipient.address);
    await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
    const after = await ethers.provider.getBalance(recipient.address);
    expect(after - before).to.equal(ethers.parseEther("0.05"));
    expect(await f.spentCommitments(commit)).to.equal(true);
  });

  it("re-using a commitment reverts", async () => {
    const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-2"));
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
    const sig = await ticket(commit, recipient.address, validUntil);
    await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
    await expect(
      f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
    ).to.be.revertedWithCustomError(f, "CommitmentSpent");
  });

  it("expired ticket rejected", async () => {
    const commit = ethers.keccak256(ethers.toUtf8Bytes("expired"));
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 5);
    const sig = await ticket(commit, recipient.address, validUntil);
    await ethers.provider.send("evm_increaseTime", [10]);
    await ethers.provider.send("evm_mine", []);
    await expect(
      f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
    ).to.be.revertedWithCustomError(f, "TicketExpired");
  });

  it("ticket signed by wrong key rejected", async () => {
    const commit = ethers.keccak256(ethers.toUtf8Bytes("wrong-key"));
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
    // signed by `outsider` (not the SIGNER)
    const inner = ethers.AbiCoder.defaultAbiCoder().encode(
      ["string", "uint256", "address", "bytes32", "address", "uint64"],
      ["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commit, recipient.address, validUntil]
    );
    const innerHash = ethers.keccak256(inner);
    const badSig = await outsider.signMessage(ethers.getBytes(innerHash));
    await expect(
      f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, badSig)
    ).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
  });

  it("recipient bound in signed message — cannot redirect", async () => {
    const commit = ethers.keccak256(ethers.toUtf8Bytes("redirect"));
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
    const sig = await ticket(commit, recipient.address, validUntil);
    // Try claiming for a different recipient with the same ticket.
    await expect(
      f.connect(outsider).claimAnonymous(commit, outsider.address, validUntil, sig)
    ).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
  });

  it("zero recipient + zero commitment rejected", async () => {
    const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
    const sig = await ticket(ethers.ZeroHash, recipient.address, validUntil);
    await expect(
      f.connect(outsider).claimAnonymous(ethers.ZeroHash, recipient.address, validUntil, sig)
    ).to.be.revertedWithCustomError(f, "BadParams");
  });
});
