/* eslint-disable */

const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants;

const ERC20FlashMintMock = artifacts.require('ERC20FlashMintMock');
const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock');

contract('ERC20FlashMint', function (accounts) {
  const [ initialHolder, other ] = accounts;

  const name = 'My Token';
  const symbol = 'MTKN';

  const initialSupply = new BN(100);
  const loanAmount = new BN(10000000000000);

  beforeEach(async function () {
    this.token = await ERC20FlashMintMock.new(name, symbol, initialHolder, initialSupply);
  });

  describe('maxFlashLoan', function () {
    it('token match', async function () {
      expect(await this.token.maxFlashLoan(this.token.address)).to.be.bignumber.equal(MAX_UINT256.sub(initialSupply));
    });

    it('token mismatch', async function () {
      expect(await this.token.maxFlashLoan(ZERO_ADDRESS)).to.be.bignumber.equal('0');
    });
  });

  describe('flashFee', function () {
    it('token match', async function () {
      expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal('0');
    });

    it('token mismatch', async function () {
      await expectRevert(this.token.flashFee(ZERO_ADDRESS, loanAmount), 'ERC20FlashMint: wrong token');
    });
  });

  describe('flashLoan', function () {
    it('success', async function () {
      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
      const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x');

      await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, value: loanAmount });
      await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, value: loanAmount });
      await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, value: loanAmount });
      await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, value: initialSupply.add(loanAmount) });

      expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply);
      expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal('0');
      expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0');
    });

    it ('missing return value', async function () {
      const receiver = await ERC3156FlashBorrowerMock.new(false, true);
      await expectRevert(
        this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'),
        'ERC20FlashMint: invalid return value',
      );
    });

    it ('missing approval', async function () {
      const receiver = await ERC3156FlashBorrowerMock.new(true, false);
      await expectRevert(
        this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'),
        'ERC20FlashMint: allowance does not allow refund',
      );
    });

    it ('unavailable funds', async function () {
      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
      const data = this.token.contract.methods.transfer(other, 10).encodeABI();
      await expectRevert(
        this.token.flashLoan(receiver.address, this.token.address, loanAmount, data),
        'ERC20: burn amount exceeds balance',
      );
    });

    it ('more than maxFlashLoan', async function () {
      const receiver = await ERC3156FlashBorrowerMock.new(true, true);
      const data = this.token.contract.methods.transfer(other, 10).encodeABI();
      // _mint overflow reverts using a panic code. No reason string.
      await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data));
    });
  });
});
