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

const DAY = 24 * 60 * 60;

describe("AereSlashingInsurance", function () {
  let fund, foundation, delegator, donor, attacker;
  const COVERAGE_BPS = 5000;                       // 50% of a slash
  const PER_EVENT_CAP = ethers.parseEther("100");  // max 100 AERE per coverage
  const COOLDOWN = 3 * DAY;
  const CLAIM_DELAY = 2 * DAY;

  beforeEach(async function () {
    [foundation, delegator, donor, attacker] = await ethers.getSigners();
    const F = await ethers.getContractFactory("AereSlashingInsurance");
    fund = await F.connect(foundation).deploy(
      foundation.address, COVERAGE_BPS, PER_EVENT_CAP, COOLDOWN, CLAIM_DELAY
    );
    await fund.waitForDeployment();
    // seed the vault with native AERE
    await fund.connect(donor).donate({ value: ethers.parseEther("500") });
  });

  it("records immutables and accepts donations (donate + receive)", async function () {
    expect(await fund.FOUNDATION()).to.equal(foundation.address);
    expect(await fund.COVERAGE_BPS()).to.equal(COVERAGE_BPS);
    expect(await fund.PER_EVENT_CAP()).to.equal(PER_EVENT_CAP);
    expect(await fund.available()).to.equal(ethers.parseEther("500"));
    // plain transfer hits receive()
    await donor.sendTransaction({ to: await fund.getAddress(), value: ethers.parseEther("10") });
    expect(await fund.available()).to.equal(ethers.parseEther("510"));
    expect(await fund.totalDonated()).to.equal(ethers.parseEther("510"));
  });

  it("coverageFor applies bps then caps per event", async function () {
    // 50 AERE slash -> 25 AERE (under cap)
    expect(await fund.coverageFor(ethers.parseEther("50"))).to.equal(ethers.parseEther("25"));
    // 400 AERE slash -> 200 -> capped to 100
    expect(await fund.coverageFor(ethers.parseEther("400"))).to.equal(PER_EVENT_CAP);
  });

  it("only Foundation can propose", async function () {
    await expect(
      fund.connect(attacker).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash)
    ).to.be.revertedWithCustomError(fund, "NotFoundation");
  });

  it("full happy path: propose, wait, execute pays frozen payout to delegator", async function () {
    await fund.connect(foundation).proposeCoverage(
      delegator.address, ethers.parseEther("50"), ethers.id("slash-evidence")
    );
    const c = await fund.claims(0);
    expect(c.delegator).to.equal(delegator.address);
    expect(c.payout).to.equal(ethers.parseEther("25"));

    // cannot execute before the timelock
    await expect(fund.connect(delegator).executeCoverage(0))
      .to.be.revertedWithCustomError(fund, "TimelockNotElapsed");

    await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
    await ethers.provider.send("evm_mine", []);

    const before = await ethers.provider.getBalance(delegator.address);
    // anyone can trigger; recipient/amount are frozen. Use donor to avoid gas accounting.
    await fund.connect(donor).executeCoverage(0);
    const after = await ethers.provider.getBalance(delegator.address);
    expect(after - before).to.equal(ethers.parseEther("25"));

    expect(await fund.totalCovered()).to.equal(ethers.parseEther("25"));
    expect((await fund.claims(0)).exists).to.equal(false);
  });

  it("permissionless cancel blocks a fraudulent claim during the delay", async function () {
    // compromised-key scenario: propose a payout to the attacker
    await fund.connect(foundation).proposeCoverage(
      attacker.address, ethers.parseEther("200"), ethers.ZeroHash
    );
    // community observer cancels it
    await expect(fund.connect(donor).cancelCoverage(0, "payout to attacker"))
      .to.emit(fund, "CoverageCancelled");
    await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
    await ethers.provider.send("evm_mine", []);
    await expect(fund.connect(attacker).executeCoverage(0))
      .to.be.revertedWithCustomError(fund, "NoClaim");
  });

  it("enforces the global cooldown between executed coverages", async function () {
    // claim 0
    await fund.connect(foundation).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash);
    // claim 1
    await fund.connect(foundation).proposeCoverage(delegator.address, ethers.parseEther("50"), ethers.ZeroHash);
    await ethers.provider.send("evm_increaseTime", [CLAIM_DELAY + 1]);
    await ethers.provider.send("evm_mine", []);
    await fund.connect(donor).executeCoverage(0);
    // second execution blocked by cooldown
    await expect(fund.connect(donor).executeCoverage(1))
      .to.be.revertedWithCustomError(fund, "CooldownActive");
    // after cooldown it succeeds
    await ethers.provider.send("evm_increaseTime", [COOLDOWN + 1]);
    await ethers.provider.send("evm_mine", []);
    await fund.connect(donor).executeCoverage(1);
    expect(await fund.totalCovered()).to.equal(ethers.parseEther("50"));
  });

  it("has no admin-withdrawal path (only executeCoverage moves funds)", async function () {
    // there is no withdraw()/sweep() function in the ABI
    const names = fund.interface.fragments.filter((f) => f.type === "function").map((f) => f.name);
    for (const n of names) {
      expect(n.toLowerCase()).to.not.match(/withdraw|sweep|rescue|drain|skim/);
    }
  });
});
