const config = require('../config')

const ExtError = require('../libs/extended-error')
const Utils = require('../libs/utils')

const engine = require('../services/engine')
const webhooks = require('../services/webhooks')
const webSocket = require('../services/websocket')

const _ = require('lodash')
const LibCash = require('@developers.cash/libcash-js')

// LibCash instance
const libCash = new LibCash()
const privateKey = libCash.ECPair.fromWIF(config.wif)

/**
 * JSON Payment Protocol
 * See: https://github.com/bitpay/jsonPaymentProtocol/blob/master/v1/specification.md
 */
class JSONPaymentProtocol {
  /**
   * Payment Request
   * @todo Implement digests, signatures, etc
   */
  static async paymentRequest (req, res, invoiceDB) {
    // Log the event as a JPP Payment Request
    res.locals.event.type = 'JPP.PaymentRequest'

    // Compile outputs into friendly form for JPP
    const outputs = invoiceDB.outputs.map(output => {
      return {
        address: output.address,
        amount: output.amountNative
      }
    })

    // Compile headers and payload
    const payload = {
      network: invoiceDB.network,
      currency: 'BCH',
      requiredFeePerByte: 0,
      outputs: outputs,
      time: new Date(invoiceDB.time * 1000).toISOString(),
      expires: new Date(invoiceDB.expires * 1000).toISOString(),
      memo: invoiceDB.memo || 'Confirm Payment',
      paymentUrl: invoiceDB.service.paymentURI,
      paymentId: invoiceDB.id
    }

    const headers = this._buildHeader(payload)

    // Save payload for debugging
    res.locals.event.res.headers = JSON.stringify(headers)
    res.locals.event.res.body = JSON.stringify(payload)

    // Send the response
    res.set(headers)
      .send(payload)

    // Notify any Websockets that might be listening
    webSocket.notify(invoiceDB._id, 'requested', { invoice: invoiceDB.payloadPublic() })

    res.locals.event.status = 'completed'
  }

  /**
   * Payment Verification
   * @todo Implement digests, signatures, etc
   */
  static async paymentVerification (req, res, invoiceDB) {
    // Log the event as a JPP Payment Request
    res.locals.event.type = 'JPP.PaymentVerification'

    const body = JSON.parse(req.body)

    // Throw error if it's not the BCH chain
    if (body.currency.toLowerCase() !== 'bch') {
      throw new ExtError('Your transaction currency did not match the one on the invoice.', { httpStatusCode: 400 })
    }

    // TODO Verify Outputs

    // Send the response
    res.send({
      payment: body,
      memo: 'Transaction appears valid'
    })

    // Send Webhook Notification (if it is defined)
    if (_.get(invoiceDB, 'options.webhook')) {
      await webhooks.send(invoiceDB.options.webhook, 'verified', { invoice: invoiceDB.payload(true) })
    }

    // Notify any Websockets that might be listening
    webSocket.notify(invoiceDB._id, 'verified', { invoice: invoiceDB.payload() })
  }

  /**
   * Payment
   * @todo implement (properly)
   */
  static async payment (req, res, invoiceDB) {
    // Log the event as a BIP70 Payment Request
    res.locals.event.type = 'JPP.PaymentAck'
    res.locals.event.status = 'compiling'

    const body = JSON.parse(req.body)

    // Throw error if it's not the BCH chain
    if (body.currency.toLowerCase() !== 'bch') {
      throw new ExtError('Your transaction currency did not match the one on the invoice.', { httpStatusCode: 400 })
    }

    // Decode the transactions
    const transactions = body.transactions

    // Verify the constructed transaction matches what's in the invoice
    if (!Utils.matchesInvoice(invoiceDB, transactions)) {
      throw new Error('Transaction does not match invoice')
    }

    // Send Broadcasting Webhook Notification (if it is defined)
    if (invoiceDB.webhook && invoiceDB.webhook.broadcasting) {
      res.locals.event.status = 'webhook.broadcasting'
      await webhooks.broadcasting(invoiceDB)
    }

    // Send transactions, save txids and set broadcast date
    res.locals.event.status = 'broadcasting'
    invoiceDB.txIds = await engine.broadcastTx(transactions.map(tx => tx.toString('hex')))
    invoiceDB.broadcasted = new Date()
    await invoiceDB.save()

    // Send Broadcasted Webhook Notification (if it is defined)
    if (invoiceDB.webhook && invoiceDB.webhook.broadcasted) {
      res.locals.event.status = 'webhook.broadcasted'
      await webhooks.broadcasted(invoiceDB)
    }

    // Compile the headers and payload
    const payload = {
      payment: {
        transactions: body.transactions
      },
      memo: 'Payment successful'
    }

    const headers = {
      'Content-Type': 'application/payment-ack'
    }

    // Set in the event
    res.locals.event.res.headers = JSON.stringify(headers)
    res.locals.event.res.body = JSON.stringify(payload)

    // Send the response
    res.set(headers)
      .send(payload)

    // Notify any Websockets that might be listening
    webSocket.notify(invoiceDB._id, 'broadcasted', { invoice: invoiceDB.payloadPublic() })

    res.locals.event.status = 'completed'
  }

  static _buildHeader (payload) {
    const digest = Buffer.from(libCash.Crypto.sha256(JSON.stringify(payload)), 'utf8')
    const signature = libCash.ECPair.sign(privateKey, digest)
    return {
      digest: digest.toString('base64'),
      'x-signature-type': 'ECC',
      'x-identity': config.domain,
      'x-signature': signature.toDER().toString('base64')
    }
  }
}

module.exports = JSONPaymentProtocol