import { CeloProvider, StaticCeloProvider } from '@celo-tools/celo-ethers-wrapper'
import { providers } from 'ethers'
import { config } from 'src/config'
import { STALE_BLOCK_TIME } from 'src/consts'
import { logger } from 'src/utils/logger'
import { promiseTimeout, sleep } from 'src/utils/promises'
import { isStale } from 'src/utils/time'

let provider: CeloProvider | undefined

export function isProviderSet() {
  return !!provider
}

export async function connectToProvider() {
  const { jsonRpcUrlPrimary, jsonRpcUrlSecondary } = config

  let connectionResult = await connectToJsonRpcProvider(jsonRpcUrlPrimary)

  if (!connectionResult && jsonRpcUrlSecondary) {
    connectionResult = await connectToJsonRpcProvider(jsonRpcUrlSecondary)
  }

  if (!connectionResult) {
    throw new Error('All json rpc providers failed to connect')
  }
}

async function connectToJsonRpcProvider(url: string) {
  try {
    logger.info(`Connecting to json rpc provider: ${url}`)
    provider = new StaticCeloProvider(url, {
      name: 'Celo',
      chainId: config.chainId,
      ensAddress: config.nomspaceRegistry,
    })
    for (let i = 0; i < 3; i++) {
      const blockAndNetworkP = Promise.all([provider.getBlock('latest'), provider.getNetwork()])
      const blockAndNetwork = await promiseTimeout(blockAndNetworkP, 1000)
      if (blockAndNetwork && isProviderSynced(blockAndNetwork[0], blockAndNetwork[1])) {
        logger.info('Provider is connected')
        return true
      }
      // Otherwise wait a bit and then try again
      await sleep(1000)
    }
    throw new Error('Unable to sync after 3 attempts')
  } catch (error) {
    logger.error(`Failed to connect to json rpc provider: ${url}`, error)
    clearProvider()
    return false
  }
}

function isProviderSynced(block?: providers.Block, network?: providers.Network) {
  return (
    block &&
    block.number &&
    block.timestamp &&
    !isStale(block.timestamp * 1000, STALE_BLOCK_TIME * 6) &&
    network &&
    network.chainId === config.chainId
  )
}

export function getProvider() {
  if (!provider) {
    logger.error('Provider is not yet initialized')
    throw new Error('Attempting to use provider before initialized')
  }
  return provider
}

export function clearProvider() {
  provider = undefined
}