import {
  JsonRpcProvider,
  TransactionReceipt,
  TransactionResponse,
} from '@ethersproject/providers'
import { Contract, Wallet } from 'ethers'
import { Config, getl2Provider } from '../../../common'

import {
  getContractInterface,
  getContractFactory,
} from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/watcher'
import { Transaction } from '@ethersproject/transactions'

export const getEnvironment = async (): Promise<{
  l1Provider: JsonRpcProvider
  l2Provider: JsonRpcProvider
  l1Wallet: Wallet
  l2Wallet: Wallet
  AddressManager: Contract
  watcher: Watcher
}> => {
  const l1Provider = new JsonRpcProvider(Config.L1NodeUrlWithPort())
  const l2Provider = getl2Provider()
  const l1Wallet = new Wallet(Config.DeployerPrivateKey(), l1Provider)
  const l2Wallet = new Wallet(Config.DeployerPrivateKey(), l2Provider)

  const addressManagerAddress = Config.AddressResolverAddress()
  const addressManagerInterface = getContractInterface('Lib_AddressManager')
  const AddressManager = new Contract(
    addressManagerAddress,
    addressManagerInterface,
    l1Provider
  )

  const watcher = await initWatcher(l1Provider, l2Provider, AddressManager)

  return {
    l1Provider,
    l2Provider,
    l1Wallet,
    l2Wallet,
    AddressManager,
    watcher,
  }
}

export const initWatcher = async (
  l1Provider: JsonRpcProvider,
  l2Provider: JsonRpcProvider,
  AddressManager: Contract
) => {
  const l1MessengerAddress = await AddressManager.getAddress(
    'Proxy__OVM_L1CrossDomainMessenger'
  )
  const l2MessengerAddress = await AddressManager.getAddress(
    'OVM_L2CrossDomainMessenger'
  )
  return new Watcher({
    l1: {
      provider: l1Provider,
      messengerAddress: l1MessengerAddress,
    },
    l2: {
      provider: l2Provider,
      messengerAddress: l2MessengerAddress,
    },
  })
}

interface CrossDomainMessagePair {
  l1tx: Transaction
  l1receipt: TransactionReceipt
  l2tx: Transaction
  l2receipt: TransactionReceipt
}

export const waitForDepositTypeTransaction = async (
  l1OriginatingTx: Promise<TransactionResponse>,
  watcher: Watcher,
  l1Provider: JsonRpcProvider,
  l2Provider: JsonRpcProvider
): Promise<CrossDomainMessagePair> => {
  const res = await l1OriginatingTx
  await res.wait()

  const l1tx = await l1Provider.getTransaction(res.hash)
  const l1receipt = await l1Provider.getTransactionReceipt(res.hash)
  const [l1ToL2XDomainMsgHash] = await watcher.getMessageHashesFromL1Tx(
    res.hash
  )
  const l2receipt = (await watcher.getL2TransactionReceipt(
    l1ToL2XDomainMsgHash
  )) as TransactionReceipt
  const l2tx = await l2Provider.getTransaction(l2receipt.transactionHash)

  return {
    l1tx,
    l1receipt,
    l2tx,
    l2receipt,
  }
}

// TODO: combine these elegantly? v^v^v
export const waitForWithdrawalTypeTransaction = async (
  l2OriginatingTx: Promise<TransactionResponse>,
  watcher: Watcher,
  l1Provider: JsonRpcProvider,
  l2Provider: JsonRpcProvider
): Promise<CrossDomainMessagePair> => {
  const res = await l2OriginatingTx
  await res.wait()

  const l2tx = await l2Provider.getTransaction(res.hash)
  const l2receipt = await l2Provider.getTransactionReceipt(res.hash)
  const [l2ToL1XDomainMsgHash] = await watcher.getMessageHashesFromL2Tx(
    res.hash
  )
  const l1receipt = (await watcher.getL1TransactionReceipt(
    l2ToL1XDomainMsgHash
  )) as TransactionReceipt
  const l1tx = await l1Provider.getTransaction(l1receipt.transactionHash)

  return {
    l2tx,
    l2receipt,
    l1tx,
    l1receipt,
  }
}