// Hardhat tests for AereSettlementHub.
//
// Covers:
//   - list asset with per-tx limit + restricted-recipient flag
//   - deposit pulls full amount, routes 50bps to sink, parks the rest
//   - claim requires allowed solver + sufficient bond
//   - settle after 6h challenge window releases asset to solver
//   - challenge inside window emits event; outside window reverts
//   - per-tx limit enforcement
//   - restricted recipient enforcement
//   - bond slash routes amount to sink
//
// Run: npx hardhat test test/settlement-hub.test.js

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

const ONE = 10n ** 18n;
const SIX = 10n ** 6n;

async function deployStack() {
  const [deployer, alice, solver1, solver2, recipient] = await ethers.getSigners();

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

  // Mock USDC-like asset (6 decimals) for tests.
  const TestUSDC = await ethers.getContractFactory("MockUSDC");
  const usdc = await TestUSDC.deploy();
  await usdc.waitForDeployment();

  const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
  await burnVault.waitForDeployment();
  const fakeRouter = await (await ethers.getContractFactory("WAERE")).deploy();
  await fakeRouter.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 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();

  // Hub.
  const Hub = await ethers.getContractFactory("AereSettlementHub");
  const hub = await Hub.deploy(await sink.getAddress());
  await hub.waitForDeployment();

  return { deployer, alice, solver1, solver2, recipient, WAERE, usdc, sink, hub };
}

describe("AereSettlementHub", function () {
  it("deposit pulls full amount and parks solverPortion", async function () {
    const { deployer, alice, recipient, WAERE, hub } = await deployStack();

    // List WAERE as an asset (use WAERE here because AereSink.flush handles it via fast path).
    await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);

    const amount = 100n * ONE;
    await WAERE.connect(alice).deposit({ value: amount });
    await WAERE.connect(alice).approve(await hub.getAddress(), amount);

    const intentId = ethers.id("intent1");
    await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);

    const fee = (amount * 50n) / 10_000n;
    const solverPortion = amount - fee;
    expect(await WAERE.balanceOf(await hub.getAddress())).to.equal(solverPortion);

    const intent = await hub.intents(intentId);
    expect(intent.amount).to.equal(solverPortion);
    expect(intent.depositor).to.equal(alice.address);
    expect(intent.recipient).to.equal(recipient.address);
    expect(intent.open).to.equal(true);
  });

  it("claim requires allowed solver AND sufficient bond", async function () {
    const { deployer, alice, solver1, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 50n * ONE, 0, false);

    const amount = 100n * ONE;
    await WAERE.connect(alice).deposit({ value: amount });
    await WAERE.connect(alice).approve(await hub.getAddress(), amount);
    const intentId = ethers.id("intent2");
    await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);

    // Solver not allowed → revert.
    await expect(hub.connect(solver1).claim(intentId, "0x")).to.be.revertedWithCustomError(hub, "NotAllowedSolver");

    // Foundation allows solver1.
    await hub.connect(deployer).setSolverAllowed(await WAERE.getAddress(), solver1.address, true);

    // Solver1 has no bond → revert InsufficientBond.
    await expect(hub.connect(solver1).claim(intentId, "0x")).to.be.revertedWithCustomError(hub, "InsufficientBond");

    // Post the bond.
    const bondAmt = 50n * ONE;
    await WAERE.connect(alice).deposit({ value: bondAmt });
    await WAERE.connect(alice).approve(await hub.getAddress(), bondAmt);
    await hub.connect(alice).depositSolverBond(await WAERE.getAddress(), solver1.address, bondAmt);

    await hub.connect(solver1).claim(intentId, "0x" + "ab".repeat(8));
    const intent = await hub.intents(intentId);
    expect(intent.solver).to.equal(solver1.address);
    expect(intent.claimedAt).to.not.equal(0);
  });

  it("settle after 6h window releases solverPortion", async function () {
    const { deployer, alice, solver1, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 1n, 0, false);
    await hub.connect(deployer).setSolverAllowed(await WAERE.getAddress(), solver1.address, true);

    const amount = 50n * ONE;
    await WAERE.connect(alice).deposit({ value: amount + 1n });
    await WAERE.connect(alice).approve(await hub.getAddress(), amount + 1n);
    await hub.connect(alice).depositSolverBond(await WAERE.getAddress(), solver1.address, 1n);
    const intentId = ethers.id("intent3");
    await hub.connect(alice).deposit(await WAERE.getAddress(), amount, intentId, recipient.address);

    await hub.connect(solver1).claim(intentId, "0x");

    await expect(hub.settle(intentId)).to.be.revertedWithCustomError(hub, "WithinChallengeWindow");

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

    const solverPortion = amount - (amount * 50n) / 10_000n;
    const balBefore = await WAERE.balanceOf(solver1.address);
    await hub.settle(intentId);
    expect(await WAERE.balanceOf(solver1.address) - balBefore).to.equal(solverPortion);
  });

  it("per-tx limit enforces", async function () {
    const { alice, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 0, 10n * ONE, false);

    await WAERE.connect(alice).deposit({ value: 100n * ONE });
    await WAERE.connect(alice).approve(await hub.getAddress(), 100n * ONE);

    await expect(
      hub.connect(alice).deposit(await WAERE.getAddress(), 11n * ONE, ethers.id("intent4"), recipient.address)
    ).to.be.revertedWithCustomError(hub, "PerTxLimitExceeded");
  });

  it("restricted recipient enforced", async function () {
    const { deployer, alice, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, true);

    await WAERE.connect(alice).deposit({ value: 10n * ONE });
    await WAERE.connect(alice).approve(await hub.getAddress(), 10n * ONE);

    // Recipient not on allowlist → revert.
    await expect(
      hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent5"), recipient.address)
    ).to.be.revertedWithCustomError(hub, "RecipientNotAllowed");

    // Foundation allows recipient.
    await hub.setRecipientAllowed(await WAERE.getAddress(), recipient.address, true);
    await hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent5"), recipient.address);
  });

  it("duplicate intentId reverts", async function () {
    const { alice, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);
    await WAERE.connect(alice).deposit({ value: 20n * ONE });
    await WAERE.connect(alice).approve(await hub.getAddress(), 20n * ONE);
    await hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("dup"), recipient.address);
    await expect(
      hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("dup"), recipient.address)
    ).to.be.revertedWithCustomError(hub, "DuplicateIntent");
  });

  it("paused asset rejects deposit", async function () {
    const { alice, recipient, WAERE, hub } = await deployStack();
    await hub.listAsset(await WAERE.getAddress(), 18, 0, 0, false);
    await hub.setAssetPaused(await WAERE.getAddress(), true);

    await WAERE.connect(alice).deposit({ value: 10n * ONE });
    await WAERE.connect(alice).approve(await hub.getAddress(), 10n * ONE);
    await expect(
      hub.connect(alice).deposit(await WAERE.getAddress(), 10n * ONE, ethers.id("intent6"), recipient.address)
    ).to.be.revertedWithCustomError(hub, "AssetPausedErr");
  });
});
