import { ethers, network } from "hardhat";
import { expect } from "chai";
import { customError } from "../utilities";
import { Migrator__factory, TridentSushiRollCP, TridentSushiRollCP__factory } from "../../types";

describe.only("Migration", function () {
  let _owner, owner, chef, migrator, usdcWethLp, usdc, weth, masterDeployer, factory, Pool, snapshotId, ERC20;

  let manualMigrator: TridentSushiRollCP;

  before(async () => {
    snapshotId = await ethers.provider.send("evm_snapshot", []);

    await network.provider.request({
      method: "hardhat_reset",
      params: [
        {
          forking: {
            jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
            blockNumber: 13390000,
          },
        },
      ],
    });

    _owner = "0x9a8541ddf3a932a9a922b607e9cf7301f1d47bd1"; // timelock, owner of MasterChef
    owner = await ethers.getSigner(_owner);
    const [alice] = await ethers.getSigners();
    const BentoBox = await ethers.getContractFactory("BentoBoxV1");
    const MasterDeployer = await ethers.getContractFactory("MasterDeployer");
    const Factory = await ethers.getContractFactory("ConstantProductPoolFactory");
    const ManualMigrator = await ethers.getContractFactory<TridentSushiRollCP__factory>("TridentSushiRollCP");
    const Migrator = await ethers.getContractFactory<Migrator__factory>("Migrator");
    Pool = await ethers.getContractFactory("ConstantProductPool");
    ERC20 = await ethers.getContractFactory("ERC20Mock");
    chef = await ethers.getContractAt(mcABI, "0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd", owner);
    usdc = await ERC20.attach("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
    weth = await ERC20.attach("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
    usdcWethLp = await ERC20.attach("0x397FF1542f962076d0BFE58eA045FfA2d347ACa0"); // pid 1

    await network.provider.send("hardhat_setBalance", [chef.address, "0x100000000000000000000"]);
    await network.provider.request({
      method: "hardhat_impersonateAccount",
      params: [chef.address],
    });
    await network.provider.send("hardhat_setBalance", [_owner, "0x100000000000000000000"]);
    await usdcWethLp.connect(await ethers.getSigner(chef.address)).transfer(_owner, "0xfffffffff"); // give some LP tokens to _owner for testing purposes
    await network.provider.request({
      method: "hardhat_impersonateAccount",
      params: [_owner],
    });

    const bentoBox = await BentoBox.deploy(weth.address);
    masterDeployer = await MasterDeployer.deploy(0, alice.address, bentoBox.address);
    factory = await Factory.deploy(masterDeployer.address);
    migrator = await Migrator.deploy(bentoBox.address, masterDeployer.address, factory.address, chef.address);
    manualMigrator = await ManualMigrator.deploy(bentoBox.address, factory.address, masterDeployer.address);

    await masterDeployer.addToWhitelist(factory.address);
    await chef.setMigrator(migrator.address);
  });

  it("Should prepare for migration in chef", async () => {
    const _migrator = await chef.migrator();
    expect(_migrator).to.be.eq(migrator.address);
  });

  it("Should migrate successfully from chef", async () => {
    const oldTotalSupply = await usdcWethLp.totalSupply();
    const oldUsdcBalance = await usdc.balanceOf(usdcWethLp.address);
    const oldWethBalance = await weth.balanceOf(usdcWethLp.address);
    const oldLpToken = (await chef.poolInfo(1)).lpToken;
    const mcBalance = await usdcWethLp.balanceOf(chef.address);
    expect(oldLpToken).to.be.eq(usdcWethLp.address, "We don't have the corect LP address");

    await chef.migrate(1);

    const newTotalSupply = await usdcWethLp.totalSupply();
    const newUsdcBalance = await usdc.balanceOf(usdcWethLp.address);
    const newWethBalance = await weth.balanceOf(usdcWethLp.address);
    const deployData = ethers.utils.defaultAbiCoder.encode(
      ["address", "address", "uint256", "bool"],
      [usdc.address, weth.address, 30, false]
    );
    const salt = ethers.utils.keccak256(deployData);
    const pool = await Pool.attach(await factory.configAddress(salt));

    expect((await pool.totalSupply()).gt(0)).to.be.true;
    expect(oldTotalSupply.gt(newTotalSupply)).to.be.true;
    expect(oldUsdcBalance.gt(newUsdcBalance)).to.be.true;
    expect(oldWethBalance.gt(newWethBalance)).to.be.true;

    // we must not allow two calls for the same pool
    await expect(chef.migrate(1)).to.be.revertedWith("ONLY_ONCE");

    const _intermediaryToken = (await chef.poolInfo(1)).lpToken;
    expect(_intermediaryToken).to.not.be.eq(oldLpToken, "we dodn't swap out tokens in masterchef");

    const intermediaryToken = await ERC20.attach(_intermediaryToken);
    const intermediaryTokenBalance = await pool.balanceOf(_intermediaryToken);
    expect(intermediaryTokenBalance.gt(0)).to.be.true;

    const newMcBalance = await intermediaryToken.balanceOf(chef.address);
    expect(newMcBalance.toString()).to.be.eq(
      newMcBalance.toString(),
      "MC didn't receive the correct amount of the intermediary token"
    );
  });

  it("Should migrate uniswap v2 style Lp positions outside of MasterChef", async () => {
    // _owner has some usdc-weth lp coins we can migrate
    const balance = (await usdcWethLp.balanceOf(_owner)).div(10);
    usdcWethLp.connect(owner).approve(manualMigrator.address, balance);
    await expect(
      manualMigrator
        .connect(owner)
        .migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, balance, balance, balance)
    ).to.be.revertedWith(customError("MinimumOutput"));
    await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, 0, 0, 0);
    const poolAddy = (await factory.getPools(usdc.address, weth.address, 0, 1))[0];
    const pool = await ERC20.attach(poolAddy);
    const newBalance = await pool.balanceOf(_owner);
    await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance.div(2), 30, false, 0, 0, 0);
    expect(newBalance.gt(0)).to.be.true;
    expect((await pool.balanceOf(_owner)).gt(newBalance)).to.be.true;
  });

  it("Should migrate from one trident CP configuration to another", async () => {
    // _owner has some usdc-weth lp coins we can migrate
    const balance = (await usdcWethLp.balanceOf(_owner)).div(10);
    usdcWethLp.connect(owner).approve(manualMigrator.address, balance);
    await manualMigrator.connect(owner).migrateLegacyToCP(usdcWethLp.address, balance, 30, false, 0, 0, 0);

    const poolAddy = (await factory.getPools(usdc.address, weth.address, 0, 1))[0];
    const pool = await ERC20.attach(poolAddy);
    const poolBalance = await pool.balanceOf(_owner);
    pool.connect(owner).approve(manualMigrator.address, poolBalance);

    await manualMigrator.connect(owner).migrateCP(poolAddy, poolBalance, 30, true, 0, 0, 0);

    const newPoolAddy = (await factory.getPools(usdc.address, weth.address, 1, 1))[0];
    const newPool = await ERC20.attach(newPoolAddy);
    const newPoolBalance = await newPool.balanceOf(_owner);

    expect((await pool.balanceOf(_owner)).eq(0)).to.be.true;
    expect(poolBalance.gt(newPoolBalance)).to.be.true; // balance won't be equal since the pool burns some liquidity
    expect(poolBalance.lt(newPoolBalance.add(10001))).to.be.true;
  });

  after(async () => {
    await network.provider.request({
      method: "hardhat_reset",
      params: [],
    });
    // disable forking and reset to not affect the wholes repo's test suite
    await network.provider.send("evm_revert", [snapshotId]);
  });
});

const mcABI = [
  {
    inputs: [{ internalType: "uint256", name: "_pid", type: "uint256" }],
    name: "migrate",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "migrator",
    outputs: [{ internalType: "contract IMigratorChef", name: "", type: "address" }],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "contract IMigratorChef",
        name: "_migrator",
        type: "address",
      },
    ],
    name: "setMigrator",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    name: "poolInfo",
    outputs: [
      {
        internalType: "contract IERC20",
        name: "lpToken",
        type: "address",
      },
      {
        internalType: "uint256",
        name: "allocPoint",
        type: "uint256",
      },
      {
        internalType: "uint256",
        name: "lastRewardBlock",
        type: "uint256",
      },
      {
        internalType: "uint256",
        name: "accSushiPerShare",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
];