import { id } from 'ethers/lib/utils'
// @ts-ignore
import helper from 'ganache-time-traveler'
// @ts-ignore
import { BN, expectRevert } from '@openzeppelin/test-helpers'
import {
  rate1,
  daiDebt1,
  WETH,
  daiTokens1,
  wethTokens1,
  chaiTokens1,
  spot,
  toRay,
  mulRay,
  divRay,
  bnify,
  almostEqual,
  precision,
  ZERO,
} from './shared/utils'
import { YieldEnvironment, Contract } from './shared/fixtures'
import { assert, expect } from 'chai'

contract('Unwind - Treasury', async (accounts) => {
  let [owner, user] = accounts

  let snapshot: any
  let snapshotId: string

  let env: YieldEnvironment

  let dai: Contract
  let vat: Contract
  let controller: Contract
  let treasury: Contract
  let weth: Contract
  let liquidations: Contract
  let unwind: Contract
  let end: Contract
  let chai: Contract

  let maturity1: number
  let maturity2: number

  const tag = divRay(toRay(0.9), spot)
  const treasuryDebt = mulRay(wethTokens1, spot).sub(1000) // No need to max out the debt of the Treasury to the last wei
  const taggedWeth = mulRay(treasuryDebt, tag)
  const fix = divRay(toRay(1.1), spot)
  const fixedWeth = mulRay(treasuryDebt, fix)

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

    env = await YieldEnvironment.setup([])
    controller = env.controller
    treasury = env.treasury
    liquidations = env.liquidations
    unwind = env.unwind

    vat = env.maker.vat
    dai = env.maker.dai
    weth = env.maker.weth
    end = env.maker.end
    chai = env.maker.chai

    // Allow owner to interact directly with Treasury, not for production
    const treasuryFunctions = ['pushDai', 'pullDai', 'pushChai', 'pullChai', 'pushWeth', 'pullWeth'].map((func) =>
      id(func + '(address,uint256)')
    )
    await treasury.batchOrchestrate(owner, treasuryFunctions)
    await end.rely(owner, { from: owner }) // `owner` replaces MKR governance
  })

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

  describe('with posted weth', () => {
    beforeEach(async () => {
      await weth.deposit({ from: owner, value: wethTokens1 })
      await weth.approve(treasury.address, wethTokens1, { from: owner })
      await treasury.pushWeth(owner, wethTokens1, { from: owner })

      assert.equal(
        (await vat.urns(WETH, treasury.address)).ink,
        wethTokens1,
        'Treasury should have ' + wethTokens1 + ' weth wei as collateral'
      )
    })

    it('does not allow to unwind if MakerDAO is live', async () => {
      await expectRevert(unwind.unwind({ from: owner }), 'Unwind: MakerDAO not shutting down')
    })

    describe('with Dss unwind initiated and tag defined', () => {
      beforeEach(async () => {
        await end.cage({ from: owner })
        await end.setTag(WETH, tag, { from: owner })
      })

      it('allows to unwind', async () => {
        await unwind.unwind({ from: owner })

        assert.equal(await unwind.live(), false, 'Unwind should be activated')
        assert.equal(await treasury.live(), false, 'Treasury should not be live')
        assert.equal(await controller.live(), false, 'Controller should not be live')
        assert.equal(await liquidations.live(), false, 'Liquidations should not be live')
      })

      describe('with fyDai in unwind', () => {
        beforeEach(async () => {
          await unwind.unwind({ from: owner })
        })

        it('allows to free system collateral without debt', async () => {
          await unwind.settleTreasury({ from: owner })

          assert.equal(
            await weth.balanceOf(unwind.address, { from: owner }),
            wethTokens1,
            'Treasury should have ' +
              wethTokens1 +
              ' weth in hand, instead has ' +
              (await weth.balanceOf(unwind.address, { from: owner }))
          )
        })

        it('does not allow to push or pull assets', async () => {
          await expectRevert(
            treasury.pushWeth(user, wethTokens1, { from: owner }),
            'Treasury: Not available during unwind'
          )
          await expectRevert(
            treasury.pushChai(user, chaiTokens1, { from: owner }),
            'Treasury: Not available during unwind'
          )
          await expectRevert(
            treasury.pushDai(user, daiTokens1, { from: owner }),
            'Treasury: Not available during unwind'
          )
          await expectRevert(treasury.pullWeth(owner, 1, { from: owner }), 'Treasury: Not available during unwind')
          await expectRevert(treasury.pullChai(owner, 1, { from: owner }), 'Treasury: Not available during unwind')
          await expectRevert(treasury.pullDai(owner, 1, { from: owner }), 'Treasury: Not available during unwind')
        })
      })
    })

    describe('with debt', () => {
      beforeEach(async () => {
        await treasury.pullDai(owner, treasuryDebt, { from: owner })
        expect((await vat.urns(WETH, treasury.address)).art).to.be.bignumber.gt(ZERO)
        expect(await treasury.debt()).to.be.bignumber.gt(ZERO)

        // Adding some extra unlocked collateral
        await weth.deposit({ from: owner, value: 1 })
        await weth.approve(treasury.address, 1, { from: owner })
        await treasury.pushWeth(owner, 1, { from: owner })
      })

      describe('with unwind initiated', () => {
        beforeEach(async () => {
          await end.cage({ from: owner })
          await end.setTag(WETH, tag, { from: owner })
          await unwind.unwind({ from: owner })
        })

        it('allows to settle treasury debt', async () => {
          await unwind.settleTreasury({ from: owner })

          almostEqual(
            await weth.balanceOf(unwind.address, { from: owner }),
            bnify(wethTokens1).sub(taggedWeth).toString(),
            precision
          )
        })
      })
    })

    describe('with savings', () => {
      beforeEach(async () => {
        await env.maker.getDai(owner, daiTokens1, rate1)

        await dai.approve(treasury.address, daiTokens1, { from: owner })
        await treasury.pushDai(owner, daiTokens1, { from: owner })

        assert.equal(
          await chai.balanceOf(treasury.address),
          chaiTokens1,
          'Treasury should have ' + daiTokens1 + ' savings (as chai).'
        )
      })

      describe('with Dss unwind initiated and fix defined', () => {
        beforeEach(async () => {
          await env.maker.getDai(user, bnify(daiTokens1).mul(2), rate1)

          await end.cage({ from: owner })
          await end.setTag(WETH, tag, { from: owner })
          await end.setDebt(1, { from: owner })
          await end.setFix(WETH, fix, { from: owner })

          // Settle some random guy's debt for end.sol to have weth
          await end.skim(WETH, user, { from: user })

          await unwind.unwind({ from: owner })
        })

        it('allows to cash dai for weth', async () => {
          assert.equal(await vat.gem(WETH, unwind.address), 0, 'Unwind should have no weth in WethJoin')

          await unwind.cashSavings({ from: owner })

          // Fun fact, MakerDAO rounds in your favour when determining how much collateral to take to settle your debt.
          assert.equal(await chai.balanceOf(treasury.address), 0, 'Treasury should have no savings (as chai).')
          almostEqual(
            await weth.balanceOf(unwind.address, { from: owner }),
            fixedWeth.toString(),
            precision // We are off by more than the usual precision here, not a big deal
          )
        })
      })
    })
  })
})