'use strict'

const _ = require('lodash')

const { ElectrumCluster } = require('electrum-cash')
const Invoice = require('../models/invoices')
const webhooks = require('../services/webhooks')

/**
 * @todo Abstract out so other engines can be plugged in
 */
class Engine {
  constructor () {
    this.electrum = null
  }

  /**
   * If this is a daemon-like process, you should instantiate it here.
   */
  async start () {
    // Initialize an electrum cluster where 2 out of 3 needs to be consistent, polled randomly with fail-over.
    this.electrum = new ElectrumCluster('Electrum cluster example', '1.4.1', 1, 1)

    // Add some servers to the cluster.
    this.electrum.addServer('bch.imaginary.cash')
    this.electrum.addServer('electroncash.de')
    this.electrum.addServer('electroncash.dk')
    this.electrum.addServer('electron.jochen-hoenicke.de', 51002)
    this.electrum.addServer('electrum.imaginary.cash')
    this.electrum.addServer('bitcoincash.network')
    this.electrum.addServer('bch0.kister.net')

    // Notify upon events
    this.electrum.on('connected', () => this.onEvent('Connected'))
    this.electrum.on('disconnected', () => this.onEvent('Disonnected'))
    this.electrum.on('degraded', () => this.onEvent('Degraded'))
    this.electrum.on('disabled', () => this.onEvent('Disabled'))
    this.electrum.on('ready', () => this.onEvent('Ready'))

    // Wait for enough connections to be available.
    await this.electrum.ready()

    // Subscribe to new blocks (so that we can check if our tx's confirmed)
    await this.electrum.subscribe((block) => this._checkForConfirmedTxs(block), 'blockchain.headers.subscribe')

    // Clean up empty and expired invoices
    // setInterval(this._cleanAbandonedInvoices, 1 * 60 * 1000)
  }

  /**
   *
   */
  async onEvent (message) {
    console.log(`[Electrum] ${message}`)
  }

  /**
   * Broadcast transaction(s) to the network.
   * @param txs Single string or array
   * @return Array of txids
   */
  async broadcastTx (txs) {
    if (typeof txs === 'string') {
      txs = [txs]
    }

    const txIds = []

    for (let i = 0; i < txs.length; i++) {
      const txId = await this.electrum.request('blockchain.transaction.broadcast', txs[i])

      if (typeof txId !== 'string') {
        console.log(txId)
        throw new Error('Failed to send transaction.')
      }

      txIds.push(txId)
    }

    return txIds
  }

  async _checkForConfirmedTxs (block) {
    // Find transactions pending confirmation
    const pendingConfirmation = await Invoice.find({
      broadcasted: { $exists: true },
      confirmed: { $exists: false }
    })

    // Loop through each transaction and check if confirmed
    for (const invoice of pendingConfirmation) {
      const event = {
        type: 'TransactionConfirmed',
        status: 'processing'
      }

      try {
        let confirmed = true

        for (let j = 0; j < invoice.txIds.length; j++) {
          const tx = await this.electrum.request('blockchain.transaction.get', invoice.txIds[j], true)
          if (!tx.confirmations) {
            confirmed = false
            break
          }
        }

        if (confirmed) {
          // TODO investigate why Array sometimes returned
          if (Array.isArray(block)) {
            invoice.confirmed = block[block.length - 1].height
          } else {
            invoice.confirmed = block.height
          }

          // Send Confirmed Webhook Notification (if it is defined)
          if (_.get(invoice, 'webhook.confirmed')) {
            event.status = 'webhook.confirmed'
            await webhooks.confirmed(invoice)
          }
        }

        event.status = 'completed'
      } catch (err) {
        console.log(err)
        invoice.message = err.message
      } finally {
        invoice.events.push(event)
        await invoice.save()
      }
    }
  }

  async _cleanAbandonedInvoices () {
    console.log('cleaning')

    await Invoice.deleteMany({
      events: { $size: 0 },
      expired: { $gt: new Date().getTime() }
    })
  }
}

const engine = new Engine()

module.exports = engine