import hre, { ethers, network as networkConfig } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { FuseStakingV3 } from "../../types";
import { expect } from "chai";
import * as waffle from "ethereum-waffle";
import { abi as ubiabi } from "../../artifacts/contracts/ubi/UBIScheme.sol/UBIScheme.json";
import { BigNumber } from "ethers";
export const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";

describe("FuseStakingV3", () => {
  let staking: FuseStakingV3;
  let founder, staker1, staker2;
  let signers;

  let uniswap: waffle.MockContract, uniswapFactory, uniswapPair, gdusdcPair, fusefusdPair, ubiMock;

  const deployMocks = async () => {
    let u = await hre.artifacts.readArtifact("Uniswap");
    let uf = await hre.artifacts.readArtifact("UniswapFactory");
    let up = await hre.artifacts.readArtifact("UniswapPair");
    uniswap = await waffle.deployMockContract(signers[0], u.abi);
    uniswapFactory = await waffle.deployMockContract(signers[0], uf.abi);
    uniswapPair = await waffle.deployMockContract(signers[0], up.abi);

    gdusdcPair = await waffle.deployMockContract(signers[0], up.abi);
    fusefusdPair = await waffle.deployMockContract(signers[0], up.abi);

    ubiMock = await waffle.deployMockContract(signers[0], ubiabi);
    await uniswap.mock.factory.returns(uniswapFactory.address);
    await uniswap.mock.WETH.returns(signers[3].address);
    await uniswapFactory.mock.getPair.returns(uniswapPair.address);
    await uniswapFactory.mock.getPair
      .withArgs(ethers.constants.AddressZero, "0x620fd5fa44BE6af63715Ef4E65DDFA0387aD13F5")
      .returns(gdusdcPair.address);

    await uniswapFactory.mock.getPair
      .withArgs(signers[3].address, "0x249BE57637D8B013Ad64785404b24aeBaE9B098B")
      .returns(fusefusdPair.address);

    await uniswapPair.mock.getReserves.returns(ethers.utils.parseEther("1000"), "100000", "0");
    await gdusdcPair.mock.getReserves.returns("4984886100", "10789000000", "0");
    await fusefusdPair.mock.getReserves.returns(
      ethers.utils.parseEther("10000"),
      ethers.utils.parseEther("2000"), //200$ fusd 18 decimals
      "0"
    );
  };

  before(async () => {
    signers = await ethers.getSigners();
    [founder, staker1, staker2] = signers.map(_ => _.address);
    await deployMocks();

    let network = networkConfig.name;
    staking = (await (await ethers.getContractFactory("FuseStakingV3")).deploy()) as FuseStakingV3;

    await staking.initialize(uniswap.address, NULL_ADDRESS);
    await staking.setUBIScheme(ubiMock.address);
  });

  it("should have owner", async () => {
    expect(await staking.owner()).to.be.equal(founder);
  });

  it("should calc quantity with slippage", async () => {
    const res = await staking["calcMaxTokenWithPriceImpact(uint256,uint256,uint256)"](
      "6917100025787759640000",
      "265724494",
      ethers.utils.parseEther("500")
    );

    // const fuseQuantity = ethers.utils.formatEther(res);
    expect(res.maxToken).to.gt(0);
    expect(res.maxToken).to.equal(BigNumber.from("6917100025787759640000").mul(6).div(1000));
    expect(res.tokenOut).to.equal(1580111);
  });

  it("should calc quantity with uniswap mock", async () => {
    const res = await staking["calcMaxFuseWithPriceImpact(uint256)"](ethers.utils.parseEther("500"));

    // const fuseQuantity = ethers.utils.formatEther(res);
    expect(res.fuseAmount).to.gt(0);
    expect(res.fuseAmount).to.equal(ethers.utils.parseEther("6"));

    await uniswapPair.mock.getReserves.returns(ethers.utils.parseEther("100"), "500000", "0");
    const res2 = await staking["calcMaxFuseWithPriceImpact(uint256)"](ethers.utils.parseEther("500"));

    expect(res2.fuseAmount).to.equal(ethers.utils.parseEther("0.6"));
  });

  it("should calculate gd/usdc quantity with 0 price impact ", async () => {
    const res = await staking["calcMaxFuseUSDCWithPriceImpact(uint256)"](ethers.utils.parseEther("10"));
    //exchanging 10 fuse which are equal 2$ USDC should have no significant price impact on usdc/gd swap so we should be able to swap the whole 10
    expect(res.maxFuse).to.gt(0);
    expect(res.maxFuse).to.equal(ethers.utils.parseEther("10"));
  });

  it("should detect gd/usdc price impact", async () => {
    const res = await staking["calcMaxFuseUSDCWithPriceImpact(uint256)"](ethers.utils.parseEther("10000"));
    expect(res.maxFuse).to.lt(ethers.utils.parseEther("10000"));
    expect(res.maxFuse).to.equal(ethers.utils.parseEther("323.67")); //on fuse swap it was around 335$ on above gd/usdc reserves that reaches 3% impact, that means 335*5=1675fuse
  });

  it("should match fuseswap and allow to exchange +-4600 fuse to G$", async () => {
    //G$/usdc reserves 52,841,23400/10410000000
    //fuse/fusd reserves 619085*1e18/42905*1e18

    await gdusdcPair.mock.getReserves.returns("5284123400", "10410000000", "0");
    await fusefusdPair.mock.getReserves.returns(
      ethers.utils.parseEther("619085"),
      ethers.utils.parseEther("42905"),
      "0"
    );

    const res = await staking["calcMaxFuseUSDCWithPriceImpact(uint256)"](ethers.utils.parseEther("10000"));
    expect(res.maxFuse).to.lt(ethers.utils.parseEther("1000"));
    expect(res.maxFuse).to.gt(ethers.utils.parseEther("450"));
  });
});
