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

// Local validation of the AereGovernor + TimelockController binding machinery
// (block-number clock). Mirrors scripts/deploy-governance-stack.js self-test but
// drives block progression with evm_mine.
async function mine(n = 1) {
  for (let i = 0; i < n; i++) await network.provider.send("evm_mine");
}

describe("AereGovernor binding self-test (local)", function () {
  it("propose -> vote -> queue -> execute flips an owned target", async function () {
    const [deployer] = await ethers.getSigners();

    const Votes = await ethers.getContractFactory("GovSelfTestVotes");
    const votes = await Votes.deploy();
    await votes.waitForDeployment();
    await (await votes.mint(deployer.address, ethers.parseEther("1000000"))).wait();
    await (await votes.delegate(deployer.address)).wait();

    const TL = await ethers.getContractFactory("TimelockController");
    const timelock = await TL.deploy(0, [], [], deployer.address);
    await timelock.waitForDeployment();

    const Gov = await ethers.getContractFactory("AereGovernor");
    const gov = await Gov.deploy(await votes.getAddress(), await timelock.getAddress(), 1, 5, 0, 4);
    await gov.waitForDeployment();

    const PROPOSER = await timelock.PROPOSER_ROLE();
    const EXECUTOR = await timelock.EXECUTOR_ROLE();
    const CANCELLER = await timelock.CANCELLER_ROLE();
    await (await timelock.grantRole(PROPOSER, await gov.getAddress())).wait();
    await (await timelock.grantRole(EXECUTOR, await gov.getAddress())).wait();
    await (await timelock.grantRole(CANCELLER, await gov.getAddress())).wait();

    const Target = await ethers.getContractFactory("GovSelfTestTarget");
    const target = await Target.deploy();
    await target.waitForDeployment();
    await (await target.transferOwnership(await timelock.getAddress())).wait();
    expect(await target.owner()).to.equal(await timelock.getAddress());
    expect(await target.value()).to.equal(0n);

    await mine(2);

    const targets = [await target.getAddress()];
    const values = [0n];
    const calldatas = [target.interface.encodeFunctionData("setValue", [42])];
    const description = "local selftest setValue(42)";
    const descHash = ethers.id(description);
    const proposalId = await gov.hashProposal(targets, values, calldatas, descHash);

    await (await gov.propose(targets, values, calldatas, description)).wait();
    const snapshot = await gov.proposalSnapshot(proposalId);
    const deadline = await gov.proposalDeadline(proposalId);

    // advance past snapshot -> Active
    const cur = await ethers.provider.getBlockNumber();
    await mine(Number(snapshot) - cur + 1);
    expect(Number(await gov.state(proposalId))).to.equal(1); // Active

    await (await gov.castVote(proposalId, 1)).wait();

    const cur2 = await ethers.provider.getBlockNumber();
    await mine(Number(deadline) - cur2 + 1);
    expect(Number(await gov.state(proposalId))).to.equal(4); // Succeeded

    await (await gov.queue(targets, values, calldatas, descHash)).wait();
    expect(Number(await gov.state(proposalId))).to.equal(5); // Queued

    await mine(1);
    await (await gov.execute(targets, values, calldatas, descHash)).wait();
    expect(Number(await gov.state(proposalId))).to.equal(7); // Executed
    expect(await target.value()).to.equal(42n);
  });

  it("gAERE adapter is non-transferable and zero-supply against empty staking", async function () {
    const [deployer, other] = await ethers.getSigners();
    const Mock = await ethers.getContractFactory("MockStakingForVotes");
    const stakingMock = await Mock.deploy();
    await stakingMock.waitForDeployment();
    const lockedMock = await Mock.deploy();
    await lockedMock.waitForDeployment();

    const G = await ethers.getContractFactory("AereStakedGovVotes");
    const g = await G.deploy(await stakingMock.getAddress(), await lockedMock.getAddress());
    await g.waitForDeployment();

    // empty staking -> zero power
    expect(await g.stakePowerOf(deployer.address)).to.equal(0n);
    await (await g.sync(deployer.address)).wait();
    expect(await g.totalSupply()).to.equal(0n);

    // seed mock delegation, sync mints mirrored balance
    await (await stakingMock.setValidator(deployer.address, ethers.parseEther("500"))).wait();
    await (await stakingMock.setDelegation(deployer.address, other.address, ethers.parseEther("1234"))).wait();
    expect(await g.stakePowerOf(other.address)).to.equal(ethers.parseEther("1234"));
    expect(await g.stakePowerOf(deployer.address)).to.equal(ethers.parseEther("500")); // self-stake
    await (await g.sync(other.address)).wait();
    expect(await g.balanceOf(other.address)).to.equal(ethers.parseEther("1234"));

    // non-transferable
    await expect(g.connect(other).transfer(deployer.address, 1n)).to.be.revertedWith("gAERE: non-transferable mirror");

    // unstake -> sync burns
    await (await stakingMock.setDelegation(deployer.address, other.address, 0n)).wait();
    await (await g.sync(other.address)).wait();
    expect(await g.balanceOf(other.address)).to.equal(0n);
  });
});
