// RWATransferAdapter tests.

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

const ONE6  = 10n ** 6n;
const ONE18 = 10n ** 18n;

async function deployStack(opts = {}) {
  const [deployer, foundation, alice, liquidator] = await ethers.getSigners();

  const ERC20 = await ethers.getContractFactory("MockERC20Lending");
  const buidl = await ERC20.deploy("BUIDL.e", "BUIDL.e", 18);
  await buidl.waitForDeployment();
  const usdce = await ERC20.deploy("USDC.e", "USDC.e", 6);
  await usdce.waitForDeployment();

  // Real oracle infra.
  const Agg = await ethers.getContractFactory("MockAggregator");
  const aggBuidl = await Agg.deploy(1_00000000); // $1.00
  await aggBuidl.waitForDeployment();
  const aggUsdc = await Agg.deploy(1_00000000);
  await aggUsdc.waitForDeployment();
  const Oracle = await ethers.getContractFactory("AereLendingOracle");
  const oracle = await Oracle.deploy();
  await oracle.waitForDeployment();
  await oracle.configureFeed(await buidl.getAddress(), 0, await aggBuidl.getAddress(), ethers.ZeroHash, 86400, opts.devBps ?? 5000, 8);
  await oracle.configureFeed(await usdce.getAddress(), 0, await aggUsdc.getAddress(), ethers.ZeroHash, 86400, opts.devBps ?? 5000, 8);

  const Adapter = await ethers.getContractFactory("RWATransferAdapter");
  const adapter = await Adapter.deploy(await usdce.getAddress(), await oracle.getAddress(), foundation.address, 6);
  await adapter.waitForDeployment();

  return { deployer, foundation, alice, liquidator, buidl, usdce, oracle, adapter, aggBuidl };
}

describe("RWATransferAdapter", function () {
  it("Foundation registers asset; rejects double-register + bad haircut", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    await expect(env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100n, 86400, 18
    )).to.be.revertedWithCustomError(env.adapter, "AlreadyRegistered");
    // Bad haircut > 50%.
    await expect(env.adapter.connect(env.foundation).registerAsset(
      await env.usdce.getAddress(), 6000, 100n, 86400, 6
    )).to.be.revertedWithCustomError(env.adapter, "BadHaircut");
  });

  it("non-Foundation cannot register", async function () {
    const env = await deployStack();
    await expect(env.adapter.connect(env.alice).registerAsset(
      await env.buidl.getAddress(), 100, 100n, 86400, 18
    )).to.be.revertedWithCustomError(env.adapter, "NotFoundation");
  });

  it("redeem: BUIDL.e → USDC.e at oracle price minus haircut", async function () {
    const env = await deployStack();
    // Register BUIDL with 1% haircut, $100k daily cap.
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    // Foundation seeds USDC.e treasury (would normally come from off-chain sale of swept RWA).
    await env.usdce.mint(env.foundation.address, 1_000_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 1_000_000n * ONE6);

    // Liquidator has 100 BUIDL.e at $1 → $100 → 99 USDC.e after 1% haircut.
    await env.buidl.mint(env.liquidator.address, 100n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 100n * ONE18);
    const before = await env.usdce.balanceOf(env.liquidator.address);
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 100n * ONE18);
    const after = await env.usdce.balanceOf(env.liquidator.address);
    expect(after - before).to.equal(99n * ONE6);
    // Accumulated balance for sweep grows by 100 BUIDL.e.
    const s = await env.adapter.states(await env.buidl.getAddress());
    expect(s.totalAccumulated).to.equal(100n * ONE18);
  });

  it("rate limit enforced per period", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 50n * ONE6, 86400, 18  // $50/day cap
    );
    await env.usdce.mint(env.foundation.address, 1_000_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 1_000_000n * ONE6);
    await env.buidl.mint(env.liquidator.address, 100n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 100n * ONE18);

    // 50 BUIDL.e ≈ $49.5 after haircut → under cap, ok.
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 50n * ONE18);
    // Next 5 BUIDL ≈ $4.95 → 49.5 + 4.95 = 54.45 > 50 → rate limit.
    await expect(env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 5n * ONE18))
      .to.be.revertedWithCustomError(env.adapter, "RateLimitExceeded");
  });

  it("rate limit resets after period", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 50n * ONE6, 60, 18  // $50 / 60s cap
    );
    await env.usdce.mint(env.foundation.address, 1_000_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 1_000_000n * ONE6);
    await env.buidl.mint(env.liquidator.address, 100n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 100n * ONE18);
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 50n * ONE18);
    // Advance time past period.
    await ethers.provider.send("evm_increaseTime", [120]);
    await ethers.provider.send("evm_mine", []);
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 50n * ONE18);
  });

  it("paused asset rejects redeem; can resume", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    await env.usdce.mint(env.foundation.address, 1_000_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 1_000_000n * ONE6);
    await env.buidl.mint(env.liquidator.address, 100n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 100n * ONE18);

    await env.adapter.connect(env.foundation).setAssetPaused(await env.buidl.getAddress(), true);
    await expect(env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 10n * ONE18))
      .to.be.revertedWithCustomError(env.adapter, "AssetIsPaused");
    await env.adapter.connect(env.foundation).setAssetPaused(await env.buidl.getAddress(), false);
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 10n * ONE18);
  });

  it("sweepRWA: Foundation can pull accumulated RWA to off-chain wallet", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    await env.usdce.mint(env.foundation.address, 1_000_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 1_000_000n * ONE6);
    await env.buidl.mint(env.liquidator.address, 100n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 100n * ONE18);
    await env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 100n * ONE18);

    const [, , , , securitizeAllowlisted] = await ethers.getSigners();
    await env.adapter.connect(env.foundation).sweepRWA(await env.buidl.getAddress(), 100n * ONE18, securitizeAllowlisted.address);
    expect(await env.buidl.balanceOf(securitizeAllowlisted.address)).to.equal(100n * ONE18);
    const s = await env.adapter.states(await env.buidl.getAddress());
    expect(s.totalAccumulated).to.equal(0n);
  });

  it("sweepRWA: cannot pull more than accumulated", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    await env.usdce.mint(env.foundation.address, 100_000n * ONE6);
    await env.usdce.connect(env.foundation).transfer(await env.adapter.getAddress(), 100_000n * ONE6);
    const [, , , , securitizeAllowlisted] = await ethers.getSigners();
    await expect(env.adapter.connect(env.foundation).sweepRWA(await env.buidl.getAddress(), 1n, securitizeAllowlisted.address))
      .to.be.revertedWithCustomError(env.adapter, "InsufficientSweepBalance");
  });

  it("Foundation cannot touch USDC.e treasury directly (no withdraw fn)", async function () {
    const env = await deployStack();
    expect(env.adapter.interface.getFunction("withdraw")).to.equal(null);
    // sweepRWA on USDC.e itself reverts because it's not registered as an asset
    const [, , , , recipient] = await ethers.getSigners();
    await expect(env.adapter.connect(env.foundation).sweepRWA(await env.usdce.getAddress(), 1n, recipient.address))
      .to.be.revertedWithCustomError(env.adapter, "UnknownAsset");
  });

  it("insufficient payout liquidity reverts cleanly", async function () {
    const env = await deployStack();
    await env.adapter.connect(env.foundation).registerAsset(
      await env.buidl.getAddress(), 100, 100_000n * ONE6, 86400, 18
    );
    // No USDC.e seeded.
    await env.buidl.mint(env.liquidator.address, 10n * ONE18);
    await env.buidl.connect(env.liquidator).approve(await env.adapter.getAddress(), 10n * ONE18);
    await expect(env.adapter.connect(env.liquidator).redeem(await env.buidl.getAddress(), 10n * ONE18))
      .to.be.revertedWithCustomError(env.adapter, "InsufficientPayoutLiquidity");
  });
});
