// Hardhat test suite for AereCoinbaseSplitterV2.
//
// Verifies:
//   - default 37.5% / 15% / 47.5% split is correct when sink is set
//   - sink unset → sink bucket merges into rebate (V1 backwards-compat path)
//   - bps caps are enforced (burnBps ≤ 5000, sinkBps ≤ 3000, sum ≤ 10000)
//   - sink rotation goes through 7-day timelock
//   - cumulative accounting matches per-tx flows
//
// Run: npx hardhat test test/splitter-v2.test.js

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

const ONE = 10n ** 18n;

// Minimal fake burn-vault that accepts the .burn() payable call.
async function deployFakeBurnVault() {
  const src = `
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.23;
    contract FakeBurnVault {
      uint256 public total;
      receive() external payable { total += msg.value; }
      function burn() external payable { total += msg.value; }
    }
  `;
  // Compile inline by reusing the existing toolchain.
  // For convenience deploy the existing AereFeeBurnVault if present, else a stub.
  // We use existing one for parity.
  const [d] = await ethers.getSigners();
  // Try existing AereFeeBurnVault — falls back to a stub deploy if not.
  let vaultFactory;
  try {
    vaultFactory = await ethers.getContractFactory("AereFeeBurnVault");
  } catch (e) {
    throw new Error("AereFeeBurnVault not compiled — run hardhat compile first.");
  }
  const vault = await vaultFactory.deploy();
  await vault.waitForDeployment();
  return vault;
}

async function deploySplitterStack() {
  const [deployer, validator, attacker] = await ethers.getSigners();

  // Reuse existing WAERE (already deployed in artifacts).
  const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
  await WAERE.waitForDeployment();

  // Real AereFeeBurnVault.
  const vault = await deployFakeBurnVault();

  // SplitterV2.
  const splitter = await (
    await ethers.getContractFactory("AereCoinbaseSplitterV2")
  ).deploy(await vault.getAddress(), await WAERE.getAddress());
  await splitter.waitForDeployment();

  return { deployer, validator, attacker, WAERE, vault, splitter };
}

describe("AereCoinbaseSplitterV2 — 3-way split", function () {
  it("with sink unset, behaves like V1: burn 37.5% + rebate 62.5%", async function () {
    const { splitter, vault, validator } = await deploySplitterStack();

    const valBalBefore = await ethers.provider.getBalance(validator.address);
    await splitter.splitAndDistribute(validator.address, { value: 100n * ONE });

    expect(await ethers.provider.getBalance(await vault.getAddress())).to.equal(375n * ONE / 10n); // 37.5
    const valBalAfter = await ethers.provider.getBalance(validator.address);
    expect(valBalAfter - valBalBefore).to.equal(625n * ONE / 10n); // 62.5

    expect(await splitter.totalBurned()).to.equal(375n * ONE / 10n);
    expect(await splitter.totalSentToSink()).to.equal(0n);
    expect(await splitter.totalRebated()).to.equal(625n * ONE / 10n);
  });

  it("sink rotation: propose, wait 7 days, accept", async function () {
    const { splitter, attacker } = await deploySplitterStack();
    // Use attacker as a stand-in sink address for the rotation test.
    await splitter.proposeSink(attacker.address);
    await expect(splitter.acceptSink()).to.be.revertedWithCustomError(splitter, "TimelockNotElapsed");

    // Fast-forward 7 days + 1 second.
    await ethers.provider.send("evm_increaseTime", [7 * 24 * 3600 + 1]);
    await ethers.provider.send("evm_mine", []);
    await splitter.acceptSink();
    expect(await splitter.sink()).to.equal(attacker.address);
  });

  it("setBps enforces caps", async function () {
    const { splitter } = await deploySplitterStack();
    await expect(splitter.setBps(5001, 1500)).to.be.revertedWithCustomError(splitter, "BpsOutOfRange");
    await expect(splitter.setBps(3000, 3001)).to.be.revertedWithCustomError(splitter, "BpsOutOfRange");
    await expect(splitter.setBps(7000, 4000)).to.be.revertedWithCustomError(splitter, "BpsOutOfRange");
    await splitter.setBps(3750, 1500); // valid no-op
  });

  it("cancelPendingSink wipes proposal", async function () {
    const { splitter, attacker } = await deploySplitterStack();
    await splitter.proposeSink(attacker.address);
    expect(await splitter.pendingSink()).to.equal(attacker.address);
    await splitter.cancelPendingSink();
    expect(await splitter.pendingSink()).to.equal(ethers.ZeroAddress);
  });

  it("rebateBps view derives correctly from burnBps + sinkBps", async function () {
    const { splitter } = await deploySplitterStack();
    expect(await splitter.rebateBps()).to.equal(10000n - 3750n - 1500n);
    await splitter.setBps(4000, 2000);
    expect(await splitter.rebateBps()).to.equal(10000n - 4000n - 2000n);
  });

  it("zero msg.value reverts", async function () {
    const { splitter, validator } = await deploySplitterStack();
    await expect(
      splitter.splitAndDistribute(validator.address, { value: 0 })
    ).to.be.revertedWithCustomError(splitter, "ZeroAmount");
  });

  it("zero validator address reverts", async function () {
    const { splitter } = await deploySplitterStack();
    await expect(
      splitter.splitAndDistribute(ethers.ZeroAddress, { value: ONE })
    ).to.be.revertedWithCustomError(splitter, "ZeroAddress");
  });
});
