// import dotenv from 'dotenv'
// import { resolve } from 'path'
import crypto from 'crypto'
import request from 'request'
import CONFIG from './../../config'
const debug = require('debug')('ea:security')

// TODO Does this reference a local config? Why?
// dotenv.config({ path: resolve('src', 'activitypub', '.env') })

export function generateRsaKeyPair(options = {}) {
  const { passphrase = CONFIG.PRIVATE_KEY_PASSPHRASE } = options
  return crypto.generateKeyPairSync('rsa', {
    modulusLength: 4096,
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem',
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem',
      cipher: 'aes-256-cbc',
      passphrase,
    },
  })
}

// signing
export function createSignature(options) {
  const {
    privateKey,
    keyId,
    url,
    headers = {},
    algorithm = 'rsa-sha256',
    passphrase = CONFIG.PRIVATE_KEY_PASSPHRASE,
  } = options
  if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) {
    throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`)
  }
  const signer = crypto.createSign(algorithm)
  const signingString = constructSigningString(url, headers)
  signer.update(signingString)
  const signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64')
  const headersString = Object.keys(headers).reduce((result, key) => {
    return result + ' ' + key.toLowerCase()
  }, '')
  return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"`
}

// verifying
export function verifySignature(url, headers) {
  return new Promise((resolve, reject) => {
    const signatureHeader = headers.signature ? headers.signature : headers.Signature
    if (!signatureHeader) {
      debug('No Signature header present!')
      resolve(false)
    }
    debug(`Signature Header = ${signatureHeader}`)
    const signature = extractKeyValueFromSignatureHeader(signatureHeader, 'signature')
    const algorithm = extractKeyValueFromSignatureHeader(signatureHeader, 'algorithm')
    const headersString = extractKeyValueFromSignatureHeader(signatureHeader, 'headers')
    const keyId = extractKeyValueFromSignatureHeader(signatureHeader, 'keyId')

    if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) {
      debug('Unsupported hash algorithm specified!')
      resolve(false)
    }

    const usedHeaders = headersString.split(' ')
    const verifyHeaders = {}
    Object.keys(headers).forEach(key => {
      if (usedHeaders.includes(key.toLowerCase())) {
        verifyHeaders[key.toLowerCase()] = headers[key]
      }
    })
    const signingString = constructSigningString(url, verifyHeaders)
    debug(`keyId= ${keyId}`)
    request(
      {
        url: keyId,
        headers: {
          Accept: 'application/json',
        },
      },
      (err, response, body) => {
        if (err) reject(err)
        debug(`body = ${body}`)
        const actor = JSON.parse(body)
        const publicKeyPem = actor.publicKey.publicKeyPem
        resolve(httpVerify(publicKeyPem, signature, signingString, algorithm))
      },
    )
  })
}

// private: signing
function constructSigningString(url, headers) {
  const urlObj = new URL(url)
  const signingString = `(request-target): post ${urlObj.pathname}${
    urlObj.search !== '' ? urlObj.search : ''
  }`
  return Object.keys(headers).reduce((result, key) => {
    return result + `\n${key.toLowerCase()}: ${headers[key]}`
  }, signingString)
}

// private: verifying
function httpVerify(pubKey, signature, signingString, algorithm) {
  if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) {
    throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`)
  }
  const verifier = crypto.createVerify(algorithm)
  verifier.update(signingString)
  return verifier.verify(pubKey, signature, 'base64')
}

// private: verifying
// This function can be used to extract the signature,headers,algorithm etc. out of the Signature Header.
// Just pass what you want as key
function extractKeyValueFromSignatureHeader(signatureHeader, key) {
  const keyString = signatureHeader.split(',').filter(el => {
    return !!el.startsWith(key)
  })[0]

  let firstEqualIndex = keyString.search('=')
  // When headers are requested add 17 to the index to remove "(request-target) " from the string
  if (key === 'headers') {
    firstEqualIndex += 17
  }
  return keyString.substring(firstEqualIndex + 2, keyString.length - 1)
}

// Obtained from invoking crypto.getHashes()
export const SUPPORTED_HASH_ALGORITHMS = [
  'rsa-md4',
  'rsa-md5',
  'rsa-mdC2',
  'rsa-ripemd160',
  'rsa-sha1',
  'rsa-sha1-2',
  'rsa-sha224',
  'rsa-sha256',
  'rsa-sha384',
  'rsa-sha512',
  'blake2b512',
  'blake2s256',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5-sha1',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512WithRSAEncryption',
  'ssl3-md5',
  'ssl3-sha1',
  'whirlpool',
]
