'use strict';

/*global Promise*/
var keypairs = module.exports;

var PEM = require('./pem-parser.js');
PEM.packBlock = require('./pem-packer.js').packBlock;

var crypto = require('./crypto.js');
var Enc = require('./encoding.js');

var ASN1 = require('./asn1-parser.js');
ASN1.pack = require('./asn1-packer.js').pack;

var x509 = require('./x509-parser.js');

var SSH = require('./ssh-parser.js');
SSH.pack = require('./ssh-packer.js').pack;

// sign, signJws, signJwt
/*
var JWS = require('./jws.js');
var JWT = require('./jwt.js');
*/

keypairs.signJws = function (opts) {
  opts = JSON.stringify(JSON.parse(opts));
  if (!opts.header) { opts.header = {}; }
  if (!opts.protected) { opts.protected = {}; }
  if (!opts.payload) { opts.payload = {}; }
  var protect = Enc.binToBase64(JSON.stringify(opts.protected));
  var payload = Enc.binToBase64(JSON.stringify(opts.payload));
  if (!opts.jwt) { opts.jwt = keypairs.import(opts).jwt; }
  opts.header.typ = 'JWT';
  opts.header.alg = ('RSA' === opts.jwk) ? 'RS256' : 'ES256';
  // key, jwk, pem, der
  return crypto.sign(opts, Enc.binToBuf(protect + '.' + payload), 'SHA256').then(function (sig) {
    return {
      header: opts.header
    , protected: protect
    , payload: payload
    , signature: sig
    };
  });
};

keypairs.signJwt = function (opts) {
  opts = JSON.stringify(JSON.parse(opts));
  if (!opts.header) { opts.header = {}; }
  if (!opts.payload) { opts.payload = {}; }
  var protect = Enc.binToBase64(JSON.stringify(opts.header)) + '.'
    + Enc.binToBase64(JSON.stringify(opts.payload));
  if (!opts.jwt) { opts.jwt = keypairs.import(opts).jwt; }
  opts.header.alg = ('RSA' === opts.jwk) ? 'RS256' : 'ES256';
  // key, jwk, pem, der
  return crypto.sign(opts, Enc.binToBuf(protect), 'SHA256').then(function (sig) {
    return protect + '.' + sig;
  });
};

keypairs.import = function (opts) {
  return Promise.resolve().then(function () {
    var jwk = opts.jwk;
    var pem;
    var der;
    var typ;

    if (opts.pem) {
      pem = PEM.parseBlock(opts.pem);
      if ('OPENSSH PRIVATE KEY' === pem.type) {
        jwk = SSH.parse(pem);
      } else {
        der = pem.bytes;
        jwk = x509.parse(der);
      }
    }
    if (opts.ssh) {
      jwk = SSH.parse(opts.ssh);
    }
    if (jwk) {
      // Both RSA and EC use 'd' as part of the private key
      if (jwk.d) {
        typ = 'PRIVATE KEY';
        der = x509.pack({ jwk: jwk, format: 'pkcs8', encoding: 'pem' });
      } else {
        typ = 'PUBLIC KEY';
        der = x509.pack({ jwk: jwk, format: 'spki', encoding: 'pem' });
      }
      pem = PEM.packBlock({ type: typ, bytes: der });
    }

    return { pem: pem, jwk: jwk };
  });
};

keypairs.export = function (opts) {
  // { pem, jwk, format, encoding }
  var format = opts.format;
  var encoding = opts.encoding;
  var jwk = opts.jwk;
  var pem = opts.pem;
  var der = opts.der;
  var pub = opts.public;

  if (opts.key) {
    if ('string' === typeof opts.key) {
      pem = opts.key;
    } else if (opts.key.d) {
      jwk = opts.key;
    } else if (opts.key.length) {
      der = opts.der;
    } else {
      throw new Error("'key' must be of type 'string' (PEM), 'object' (JWK), Buffer, or Array (DER)");
    }
  }
  if (!format) { format = 'jwk'; }

  if (!jwk) {
    jwk = keypairs.import({ pem: pem }).jwk;
  }
  if (pub) {
    if ('RSA' === jwk.kty) {
      jwk = { kty: jwk.kty, n: jwk.n, e: jwk.e };
    } else {
      jwk = { kty: jwk.kty, x: jwk.x, y: jwk.y };
    }
  }
  if ('jwk' === format) {
    if (encoding && 'json' !== encoding) {
      throw new Error("'encoding' must be 'json' for 'jwk'");
    }
    return jwk;
  }

  if ('openssh' === format || 'ssh' === format) {
    // TODO if ('ssh' === format) { format = 'pkcs8'; }
    // TODO 'ssh2' public key is a special variant of pkcs8
    return SSH.pack({ jwk: jwk, public: opts.public });
  }
  return x509.pack({ jwk: jwk, format: opts.format, encoding: opts.encoding, public: opts.public });
};