import { id } from 'ethers/lib/utils'
// @ts-ignore
import helper from 'ganache-time-traveler'
// @ts-ignore
import { expectRevert, expectEvent } from '@openzeppelin/test-helpers'
import { WETH, daiTokens1, wethTokens1, bnify } from './shared/utils'
import { YieldEnvironmentLite, Contract } from './shared/fixtures'

contract('fyDai - Delegation', async (accounts) => {
  let [owner, holder, other] = accounts

  let maturity1: number
  let maturity2: number

  let snapshot: any
  let snapshotId: string

  let treasury: Contract
  let vat: Contract
  let weth: Contract
  let dai: Contract
  let fyDai1: Contract

  beforeEach(async () => {
    snapshot = await helper.takeSnapshot()
    snapshotId = snapshot['result']

    // Setup fyDai
    const block = await web3.eth.getBlockNumber()
    maturity1 = (await web3.eth.getBlock(block)).timestamp + 1000
    maturity2 = (await web3.eth.getBlock(block)).timestamp + 2000

    const env = await YieldEnvironmentLite.setup([maturity1, maturity2])
    treasury = env.treasury
    weth = env.maker.weth
    vat = env.maker.vat
    dai = env.maker.dai

    fyDai1 = env.fyDais[0]

    // Post collateral to MakerDAO through Treasury
    await treasury.orchestrate(owner, id('pushWeth(address,uint256)'), { from: owner })
    const initialCapital = bnify(wethTokens1).add(10).toString()
    await weth.deposit({ from: owner, value: initialCapital })
    await weth.approve(treasury.address, initialCapital, { from: owner })
    await treasury.pushWeth(owner, initialCapital, { from: owner })
    assert.equal((await vat.urns(WETH, treasury.address)).ink, initialCapital.toString())

    // Mint some fyDai the sneaky way
    await fyDai1.orchestrate(owner, id('mint(address,uint256)'), { from: owner })
    await fyDai1.mint(holder, daiTokens1, { from: owner })

    // fyDai matures
    await helper.advanceTime(1000)
    await helper.advanceBlock()
    await fyDai1.mature()

    assert.equal(await fyDai1.balanceOf(holder), daiTokens1, 'Holder does not have fyDai')
    assert.equal(await treasury.savings(), 0, 'Treasury has no savings')
  })

  afterEach(async () => {
    await helper.revertToSnapshot(snapshotId)
  })

  it('redeem is allowed for account holder', async () => {
    await fyDai1.approve(fyDai1.address, daiTokens1, { from: holder })
    await fyDai1.redeem(holder, holder, daiTokens1, { from: holder })

    assert.equal(await treasury.debt(), daiTokens1, 'Treasury should have debt')
    assert.equal(await dai.balanceOf(holder), daiTokens1, 'Holder should have dai')
  })

  it('redeem is not allowed for non designated accounts', async () => {
    await fyDai1.approve(fyDai1.address, daiTokens1, { from: holder })
    await expectRevert(fyDai1.redeem(holder, holder, daiTokens1, { from: other }), 'FYDai: Only Holder Or Delegate')
  })

  it('redeem is allowed for delegates', async () => {
    await fyDai1.approve(fyDai1.address, daiTokens1, { from: holder })
    expectEvent(await fyDai1.addDelegate(other, { from: holder }), 'Delegate', {
      user: holder,
      delegate: other,
      enabled: true,
    })
    await fyDai1.redeem(holder, holder, daiTokens1, { from: other })

    assert.equal(await treasury.debt(), daiTokens1, 'Treasury should have debt')
    assert.equal(await dai.balanceOf(holder), daiTokens1, 'Holder should have dai')
  })

  describe('with delegates', async () => {
    beforeEach(async () => {
      await fyDai1.addDelegate(other, { from: holder })
    })

    it('redeem is not allowed if delegation revoked', async () => {
      expectEvent(await fyDai1.revokeDelegate(other, { from: holder }), 'Delegate', {
        user: holder,
        delegate: other,
        enabled: false,
      })

      await expectRevert(fyDai1.redeem(holder, holder, daiTokens1, { from: other }), 'FYDai: Only Holder Or Delegate')
    })

    it('cannot add delegate again or remove delegate twice', async () => {
      await expectRevert(fyDai1.addDelegate(other, { from: holder }), 'Delegable: Already delegated')

      expectEvent(await fyDai1.revokeDelegate(other, { from: holder }), 'Delegate', {
        user: holder,
        delegate: other,
        enabled: false,
      })

      await expectRevert(fyDai1.revokeDelegate(other, { from: holder }), 'Delegable: Already undelegated')
    })
  })
})