'use strict';
const common = require('../common');
if (!common.hasCrypto)
  common.skip('missing crypto');

const assert = require('assert');
const util = require('util');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');

function test(
  publicFixture,
  privateFixture,
  algorithm,
  deterministic,
  options
) {
  let publicPem = fixtures.readKey(publicFixture);
  let privatePem = fixtures.readKey(privateFixture);
  let privateKey = crypto.createPrivateKey(privatePem);
  let publicKey = crypto.createPublicKey(publicPem);
  const privateDer = {
    key: privateKey.export({ format: 'der', type: 'pkcs8' }),
    format: 'der',
    type: 'pkcs8',
    ...options
  };
  const publicDer = {
    key: publicKey.export({ format: 'der', type: 'spki' }),
    format: 'der',
    type: 'spki',
    ...options
  };

  if (options) {
    publicPem = { ...options, key: publicPem };
    privatePem = { ...options, key: privatePem };
    privateKey = { ...options, key: privateKey };
    publicKey = { ...options, key: publicKey };
  }

  const data = Buffer.from('Hello world');
  const expected = crypto.sign(algorithm, data, privateKey);

  for (const key of [privatePem, privateKey, privateDer]) {
    crypto.sign(algorithm, data, key, common.mustSucceed((actual) => {
      if (deterministic) {
        assert.deepStrictEqual(actual, expected);
      }

      assert.strictEqual(
        crypto.verify(algorithm, data, key, actual), true);
    }));
  }

  const verifyInputs = [
    publicPem, publicKey, publicDer, privatePem, privateKey, privateDer];
  for (const key of verifyInputs) {
    crypto.verify(algorithm, data, key, expected, common.mustSucceed(
      (verified) => assert.strictEqual(verified, true)));

    crypto.verify(algorithm, data, key, Buffer.from(''), common.mustSucceed(
      (verified) => assert.strictEqual(verified, false)));
  }
}

// RSA w/ default padding
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true);
test('rsa_public.pem', 'rsa_private.pem', 'sha256', true,
     { padding: crypto.constants.RSA_PKCS1_PADDING });

// RSA w/ PSS_PADDING and default saltLength
test('rsa_public.pem', 'rsa_private.pem', 'sha256', false,
     { padding: crypto.constants.RSA_PKCS1_PSS_PADDING });
test('rsa_public.pem', 'rsa_private.pem', 'sha256', false,
     {
       padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
       saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
     });

// RSA w/ PSS_PADDING and PSS_SALTLEN_DIGEST
test('rsa_public.pem', 'rsa_private.pem', 'sha256', false,
     {
       padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
       saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
     });

// ED25519
test('ed25519_public.pem', 'ed25519_private.pem', undefined, true);
// ED448
// skip tests from electron
// https://github.com/electron/electron/blob/add374ef6a3e576fa4f73dbf68199540668a75cf/patches/node/fix_crypto_tests_to_run_with_bssl.patch#L56
if (!common.openSSLIsBoringSSL) {
test('ed448_public.pem', 'ed448_private.pem', undefined, true);

// ECDSA w/ der signature encoding
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
     false);
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384',
     false, { dsaEncoding: 'der' });

// ECDSA w/ ieee-p1363 signature encoding
test('ec_secp256k1_public.pem', 'ec_secp256k1_private.pem', 'sha384', false,
     { dsaEncoding: 'ieee-p1363' });

// DSA w/ der signature encoding
test('dsa_public.pem', 'dsa_private.pem', 'sha256',
     false);
test('dsa_public.pem', 'dsa_private.pem', 'sha256',
     false, { dsaEncoding: 'der' });

// DSA w/ ieee-p1363 signature encoding
test('dsa_public.pem', 'dsa_private.pem', 'sha256', false,
     { dsaEncoding: 'ieee-p1363' });
}

// Test Parallel Execution w/ KeyObject is threadsafe in openssl3
{
  const publicKey = {
    key: crypto.createPublicKey(
      fixtures.readKey('ec_p256_public.pem')),
    dsaEncoding: 'ieee-p1363',
  };
  const privateKey = {
    key: crypto.createPrivateKey(
      fixtures.readKey('ec_p256_private.pem')),
    dsaEncoding: 'ieee-p1363',
  };

  const sign = util.promisify(crypto.sign);
  const verify = util.promisify(crypto.verify);

  const data = Buffer.from('hello world');

  Promise.all([
    sign('sha256', data, privateKey),
    sign('sha256', data, privateKey),
    sign('sha256', data, privateKey),
  ]).then(([signature]) => {
    return Promise.all([
      verify('sha256', data, publicKey, signature),
      verify('sha256', data, publicKey, signature),
      verify('sha256', data, publicKey, signature),
    ]).then(common.mustCall());
  })
  .catch(common.mustNotCall());
}
