import { compareAddress, Config } from '@usedapp/core'
import {
  deployMockToken,
  MOCK_TOKEN_INITIAL_BALANCE,
  renderDAppHook,
  setupTestingConfig,
  TestingNetwork,
} from '@usedapp/testing'
import { expect } from 'chai'
import { BigNumber, Contract, Wallet } from 'ethers'
import { getCreate2Address, solidityKeccak256, solidityPack } from 'ethers/lib/utils'
import { INIT_CODE_HASH, UniswapV2Pair } from '../constants'
import { deployUniswapV2Pair } from '../utils/deployMockUniswapV2Pair'
import { useUniswapPrice } from './useUniswapPrice'

describe('useUniswapPrice', () => {
  let network1: TestingNetwork
  let config: Config
  let deployer: Wallet
  const DIGITS = 18
  const ONE = BigNumber.from(1)
  const RATIO = BigNumber.from(5)
  const EXP_SCALE = BigNumber.from(10).pow(DIGITS)
  let tokenA: Contract
  let tokenB: Contract
  let factory: Contract
  let pair: Contract

  async function addLiquidity(tokenAAmount: BigNumber, tokenBAmount: BigNumber) {
    await tokenA.transfer(pair.address, tokenAAmount)
    await tokenB.transfer(pair.address, tokenBAmount)
    await pair.mint(deployer.address)
  }

  function sortContracts(tokenA: Contract, tokenB: Contract) {
    return compareAddress(tokenA.address, tokenB.address) === -1 ? [tokenA, tokenB] : [tokenB, tokenA]
  }

  beforeEach(async () => {
    ;({ config, network1 } = await setupTestingConfig())
    deployer = network1.wallets[0]
    ;[tokenA, tokenB] = sortContracts(await deployMockToken(deployer), await deployMockToken(deployer))
    ;({ factory, pair } = await deployUniswapV2Pair(deployer, tokenA, tokenB))
    // RATIO = tokenAReserve / tokenBReserve = 5
    await addLiquidity(MOCK_TOKEN_INITIAL_BALANCE, MOCK_TOKEN_INITIAL_BALANCE.div(RATIO))
  })

  it('get init code hash', async () => {
    const initCodeHash = solidityKeccak256(['bytes'], [solidityPack(['bytes'], [`0x${UniswapV2Pair.bytecode}`])])
    expect(initCodeHash).to.equal(INIT_CODE_HASH)
  })

  it('compute pair address by using CREATE2', async () => {
    const salt = solidityKeccak256(['bytes'], [solidityPack(['address', 'address'], [tokenA.address, tokenB.address])])
    const computedAddress = getCreate2Address(factory.address, salt, INIT_CODE_HASH)
    expect(computedAddress).to.equal(pair.address)
  })

  it('get price', async () => {
    // base/quote (e.g. ETH/DAI): price of baseToken in quoteToken = quoteTokenReserve / baseTokenReserve
    const [numerator, denominator] = [ONE, RATIO]
    const price = numerator.mul(EXP_SCALE).div(denominator)

    const { result, waitForCurrent } = await renderDAppHook(
      () => useUniswapPrice(tokenA.address, tokenB.address, { factory: factory.address }),
      {
        config,
      }
    )

    await waitForCurrent((val) => val !== undefined)
    expect(result.error).to.be.undefined
    expect(result.current).to.eq(price)
  })
})
