'use strict'

const async = require('async')
const forge = require('node-forge')
const util = require('./util')

class CMS {
  constructor (keystore) {
    if (!keystore) {
      throw new Error('keystore is required')
    }

    this.keystore = keystore;
  }

  createAnonymousEncryptedData (name, plain, callback) {
    const self = this
    if (!Buffer.isBuffer(plain)) {
      return callback(new Error('Data is required'))
    }

    self.keystore._getPrivateKey(name, (err, key) => {
      if (err) {
        return callback(err)
      }

      try {
        const privateKey = forge.pki.decryptRsaPrivateKey(key, self.keystore._())
        util.certificateForKey(privateKey, (err, certificate) => {
          if (err) return callback(err)

          // create a p7 enveloped message
          const p7 = forge.pkcs7.createEnvelopedData()
          p7.addRecipient(certificate)
          p7.content = forge.util.createBuffer(plain)
          p7.encrypt()

          // convert message to DER
          const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
          callback(null, Buffer.from(der, 'binary'))
        })
      } catch (err) {
        callback(err)
      }
    })
  }

  readData (cmsData, callback) {
    if (!Buffer.isBuffer(cmsData)) {
      return callback(new Error('CMS data is required'))
    }

    const self = this
    let cms
    try {
      const buf = forge.util.createBuffer(cmsData.toString('binary'));
      const obj = forge.asn1.fromDer(buf)
      cms = forge.pkcs7.messageFromAsn1(obj)
    } catch (err) {
      return callback(new Error('Invalid CMS: ' + err.message))
    }

    // Find a recipient whose key we hold. We only deal with recipient certs
    // issued by ipfs (O=ipfs).
    const recipients = cms.recipients
      .filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs'))
      .filter(r => r.issuer.find(a => a.shortName === 'CN'))
      .map(r => {
        return {
          recipient: r,
          keyId: r.issuer.find(a => a.shortName === 'CN').value
        }
      })
    async.detect(
      recipients,
      (r, cb) => self.keystore.findKeyById(r.keyId, (err, info) => cb(null, !err && info)),
      (err, r) => {
        if (err) return callback(err)
        if (!r) return callback(new Error('No key found for decryption'))

        async.waterfall([
          (cb) => self.keystore.findKeyById(r.keyId, cb),
          (key, cb) => self.keystore._getPrivateKey(key.name, cb)
        ], (err, pem) => {
          if (err) return callback(err);

          const privateKey = forge.pki.decryptRsaPrivateKey(pem, self.keystore._())
          cms.decrypt(r.recipient, privateKey)
          async.setImmediate(() => callback(null, Buffer.from(cms.content.getBytes(), 'binary')))
        })
      }
    )
  }

}

module.exports = CMS
