// AereFoundationAggregator tests.
// Chainlink-compatible single-asset feed with Foundation push updates.

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

async function deploy(opts = {}) {
  const desc = opts.desc ?? "BUIDL.e/USD";
  const jumpBps = opts.jumpBps ?? 500;       // 5% per-update max
  const minHeartbeat = opts.minHeartbeat ?? 300; // 5 min
  const initialAnswer = opts.initialAnswer ?? 1_00000000n; // $1, 8 decimals
  const Agg = await ethers.getContractFactory("AereFoundationAggregator");
  const agg = await Agg.deploy(desc, jumpBps, minHeartbeat, initialAnswer);
  await agg.waitForDeployment();
  return agg;
}

describe("AereFoundationAggregator", function () {
  it("constructor sets immutable params + initial round", async function () {
    const agg = await deploy();
    expect(await agg.description()).to.equal("BUIDL.e/USD");
    expect(await agg.decimals()).to.equal(8);
    expect(await agg.maxJumpBps()).to.equal(500);
    expect(await agg.minHeartbeat()).to.equal(300);
    expect(await agg.latestRoundId()).to.equal(1n);
    expect(await agg.latestAnswer()).to.equal(1_00000000n);
  });

  it("latestRoundData returns Chainlink-compatible tuple", async function () {
    const agg = await deploy();
    const data = await agg.latestRoundData();
    expect(data[0]).to.equal(1n);            // roundId
    expect(data[1]).to.equal(1_00000000n);   // answer
    expect(data[4]).to.equal(1n);            // answeredInRound == roundId
  });

  it("Foundation can push update after heartbeat", async function () {
    const agg = await deploy();
    await ethers.provider.send("evm_increaseTime", [301]);
    await ethers.provider.send("evm_mine", []);
    await agg.updateAnswer(1_02000000n); // $1.02 — 2% jump, within 5% cap
    expect(await agg.latestRoundId()).to.equal(2n);
    expect(await agg.latestAnswer()).to.equal(1_02000000n);
  });

  it("rejects update before heartbeat elapsed", async function () {
    const agg = await deploy();
    await expect(agg.updateAnswer(1_01000000n))
      .to.be.revertedWithCustomError(agg, "HeartbeatTooSoon");
  });

  it("rejects update that exceeds max jump cap", async function () {
    const agg = await deploy({ jumpBps: 200 });   // 2% cap
    await ethers.provider.send("evm_increaseTime", [400]);
    await ethers.provider.send("evm_mine", []);
    await expect(agg.updateAnswer(1_05000000n))   // 5% > 2% cap
      .to.be.revertedWithCustomError(agg, "JumpExceeded");
  });

  it("rejects zero or negative prices", async function () {
    const agg = await deploy();
    await ethers.provider.send("evm_increaseTime", [400]);
    await ethers.provider.send("evm_mine", []);
    await expect(agg.updateAnswer(0n)).to.be.revertedWithCustomError(agg, "InvalidPrice");
  });

  it("non-owner cannot update", async function () {
    const agg = await deploy();
    const [, alice] = await ethers.getSigners();
    await expect(agg.connect(alice).updateAnswer(1_01000000n))
      .to.be.revertedWith("Ownable: caller is not the owner");
  });

  it("pause blocks new updates; freezes price (last good value persists)", async function () {
    const agg = await deploy();
    await agg.setPaused(true);
    await ethers.provider.send("evm_increaseTime", [400]);
    await ethers.provider.send("evm_mine", []);
    await expect(agg.updateAnswer(1_01000000n))
      .to.be.revertedWithCustomError(agg, "AggregatorPaused");
    // latestRoundData still returns the last good price.
    const d = await agg.latestRoundData();
    expect(d[1]).to.equal(1_00000000n);
  });

  it("getRoundData returns historical values", async function () {
    const agg = await deploy();
    await ethers.provider.send("evm_increaseTime", [400]);
    await ethers.provider.send("evm_mine", []);
    await agg.updateAnswer(1_02000000n);
    const r1 = await agg.getRoundData(1);
    expect(r1[1]).to.equal(1_00000000n);
    const r2 = await agg.getRoundData(2);
    expect(r2[1]).to.equal(1_02000000n);
    await expect(agg.getRoundData(99)).to.be.revertedWithCustomError(agg, "UnknownRound");
  });

  it("works as Chainlink feed for AereLendingOracle", async function () {
    // End-to-end: configure as the source for an asset in the lending oracle.
    const agg = await deploy();
    const Oracle = await ethers.getContractFactory("AereLendingOracle");
    const oracle = await Oracle.deploy();
    await oracle.waitForDeployment();
    const ERC20 = await ethers.getContractFactory("MockERC20Lending");
    const buidl = await ERC20.deploy("BUIDL.e", "BUIDL.e", 18);
    await buidl.waitForDeployment();

    // FeedType.ChainlinkLike = 0
    await oracle.configureFeed(await buidl.getAddress(), 0, await agg.getAddress(), ethers.ZeroHash, 86400, 200, 8);
    // configureFeed auto-pokes → lastGoodPrice set.
    const feed = await oracle.feeds(await buidl.getAddress());
    expect(feed.lastGoodPrice).to.equal(10n ** 18n); // 1e18 normalised from 1e8
    expect(await oracle.getPrice(await buidl.getAddress())).to.equal(10n ** 18n);
  });
});
