const axios = require('axios')

const config = require('./config')

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

// LibCash instance
const libCash = new LibCash()

/**
  * This class contains useful utilities for verifying Websocket and Webhook Events.
  * @example
  * // Verify signatures for Webhook Event
  * CashPay.Signatures.verifyWebhook(req.body, req.headers)
  *
  * // Verify signatures for Websocket Event
  * CashPay.Signatures.verifyEvent(payload)
  */
class Signatures {
  /**
   * <p>Refreshes the keys using the endpoint given at CashPay.config.options.endpoint.</p>
   * <p>This will be called automatically, as needed, by verifyWebhook and verifyEvent.</p>
   * <p>You should not need to call this manually.</p>
   * @example
   * CashPay.Signatures.refreshKeys()
   */
  static async refreshKeys () {
    const res = await axios.get(`${config.options.endpoint}/signingKeys/paymentProtocol.json`)
    this._keys = {
      endpoint: config.options.endpoint,
      expirationDate: res.data.expirationDate,
      publicKeys: res.data.publicKeys
    }

    return this
  }

  /**
   * Verifies the signature of a Webhook Payload
   *
   * @param {(string|object)} payload String or Object containing the payload
   * @param {object} headers HTTP Headers (requires digest, x-identity, x-signature-type and x-signature)
   * @example
   * // ExpressJS
   * CashPay.Signatures.verifyWebhook(req.body, req.headers)
   */
  static async verifyWebhook (payload, headers) {
    const signature = {
      digest: headers.digest,
      identity: headers.identity,
      signature: headers.signature,
      signatureType: headers.signatureType
    }

    return this._verify(payload, signature)
  }

  /**
   * Verifies the signature of a Webhook Payload
   *
   * @param {(string|object)} payload String or Object containing the Websocket Event payload
   * @example
   * CashPay.Signatures.verifyEvent(payload)
   */
  static async verifyEvent (payload) {
    const signature = {
      digest: payload.signature.digest,
      identity: payload.signature.identity,
      signature: payload.signature.signature,
      signatureType: payload.signature.signatureType
    }

    delete payload.signature

    return this._verify(payload, signature)
  }

  static async _verify (payload, signature) {
    // Make sure signature type is ECC
    if (signature.signatureType !== 'ECC') {
      throw new Error(`x-signature-type must be ECC (current value ${signature.signatureType})`)
    }

    // Refresh keys if they don't exist or are expired
    if (!this._keys || new Date() > new Date(this._keys.expirationDate)) {
      await this.refreshKeys()
    }

    // Convert into buffers
    if (typeof payload === 'object') {
      payload = JSON.stringify(payload)
    }

    if (typeof payload === 'string') {
      payload = Buffer.from(payload)
    }

    if (typeof signature.digest === 'string') {
      signature.digest = Buffer.from(signature.digest, 'base64')
    }

    if (typeof signature.signature === 'string') {
      signature.signature = Buffer.from(signature.signature, 'base64')
    }

    // Compare the digest (SHA256 of payload)
    const payloadDigest = libCash.Crypto.sha256(payload)

    if (Buffer.compare(payloadDigest, signature.digest)) {
      throw new Error('Payload digest did not match header digest')
    }

    const correct = this._keys.publicKeys.reduce((isValid, publicKey) => {
      const ecPair = libCash.ECPair.fromPublicKey(Buffer.from(publicKey, 'hex'))
      const result = isValid += libCash.ECPair.verify(
        ecPair,
        payloadDigest,
        signature.signature
      )
      return result
    }, false)

    if (!correct) {
      throw new Error('Signature verification failed')
    }

    return true
  }
}

Signatures._keys = null

module.exports = Signatures