'use strict';

require('dotenv').config();

var CSR = require('@root/csr');
var Enc = require('@root/encoding/base64');
var PEM = require('@root/pem');
var punycode = require('punycode');
var ACME = require('../acme.js');
var Keypairs = require('@root/keypairs');

// TODO exec npm install --save-dev CHALLENGE_MODULE
if (!process.env.CHALLENGE_OPTIONS) {
	console.error(
		'Please create a .env in the format of examples/example.env to run the tests'
	);
	process.exit(1);
}

var config = {
	env: process.env.ENV,
	email: process.env.SUBSCRIBER_EMAIL,
	domain: process.env.BASE_DOMAIN,
	challengeType: process.env.CHALLENGE_TYPE,
	challengeModule: process.env.CHALLENGE_PLUGIN,
	challengeOptions: JSON.parse(process.env.CHALLENGE_OPTIONS)
};
//config.debug = !/^PROD/i.test(config.env);
var pluginPrefix = 'acme-' + config.challengeType + '-';
var pluginName = config.challengeModule;
var plugin;

module.exports = function() {
	console.info('\n[Test] end-to-end issue certificates');

	var acme = ACME.create({
		// debug: true
		maintainerEmail: config.email,
		notify: function(ev, params) {
			console.info(
				'\t' + ev,
				params.subject || params.altname || params.domain || '',
				params.status || ''
			);
			if ('error' === ev) {
				console.error(params.action || params.type || '');
				console.error(params);
			}
		}
	});

	function badPlugin(err) {
		if ('MODULE_NOT_FOUND' !== err.code) {
			console.error(err);
			return;
		}
		console.error("Couldn't find '" + pluginName + "'. Is it installed?");
		console.error("\tnpm install --save-dev '" + pluginName + "'");
	}
	try {
		plugin = require(pluginName);
	} catch (err) {
		if (
			'MODULE_NOT_FOUND' !== err.code ||
			0 === pluginName.indexOf(pluginPrefix)
		) {
			badPlugin(err);
			process.exit(1);
		}
		try {
			pluginName = pluginPrefix + pluginName;
			plugin = require(pluginName);
		} catch (e) {
			badPlugin(e);
			process.exit(1);
		}
	}

	config.challenger = plugin.create(config.challengeOptions);
	if (!config.challengeType || !config.domain) {
		console.error(
			new Error('Missing config variables. Check you .env and the docs')
				.message
		);
		console.error(config);
		process.exit(1);
	}

	var challenges = {};
	challenges[config.challengeType] = config.challenger;

	async function happyPath(accKty, srvKty, rnd) {
		var agreed = false;
		var metadata = await acme.init(
			'https://acme-staging-v02.api.letsencrypt.org/directory'
		);

		// Ready to use, show page
		if (config.debug) {
			console.info('ACME.js initialized');
			console.info(metadata);
			console.info();
			console.info();
		}

		var accountKeypair = await Keypairs.generate({ kty: accKty });
		var accountKey = accountKeypair.private;
		if (config.debug) {
			console.info('Account Key Created');
			console.info(JSON.stringify(accountKey, null, 2));
			console.info();
			console.info();
		}

		var account = await acme.accounts.create({
			agreeToTerms: agree,
			// TODO detect jwk/pem/der?
			accountKey: accountKey,
			subscriberEmail: config.email
		});

		// TODO top-level agree
		function agree(tos) {
			if (config.debug) {
				console.info('Agreeing to Terms of Service:');
				console.info(tos);
				console.info();
				console.info();
			}
			agreed = true;
			return Promise.resolve(tos);
		}
		if (config.debug) {
			console.info('New Subscriber Account');
			console.info(JSON.stringify(account, null, 2));
			console.info();
			console.info();
		}
		if (!agreed) {
			throw new Error('Failed to ask the user to agree to terms');
		}

		var certKeypair = await Keypairs.generate({ kty: srvKty });
		var pem = await Keypairs.export({
			jwk: certKeypair.private,
			encoding: 'pem'
		});
		if (config.debug) {
			console.info('Server Key Created');
			console.info('privkey.jwk.json');
			console.info(JSON.stringify(certKeypair, null, 2));
			// This should be saved as `privkey.pem`
			console.info();
			console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
			console.info(pem);
			console.info();
		}

		// 'subject' should be first in list
		var domains = randomDomains(rnd);
		if (config.debug) {
			console.info('Get certificates for random domains:');
			console.info(
				domains
					.map(function(puny) {
						var uni = punycode.toUnicode(puny);
						if (puny !== uni) {
							return puny + ' (' + uni + ')';
						}
						return puny;
					})
					.join('\n')
			);
			console.info();
		}

		// Create CSR
		var csrDer = await CSR.csr({
			jwk: certKeypair.private,
			domains: domains,
			encoding: 'der'
		});
		var csr = Enc.bufToUrlBase64(csrDer);
		var csrPem = PEM.packBlock({
			type: 'CERTIFICATE REQUEST',
			bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
		});
		if (config.debug) {
			console.info('Certificate Signing Request');
			console.info(csrPem);
			console.info();
		}

		var results = await acme.certificates.create({
			account: account,
			accountKey: accountKey,
			csr: csr,
			domains: domains,
			challenges: challenges, // must be implemented
			customerEmail: null
		});

		if (config.debug) {
			console.info('Got SSL Certificate:');
			console.info(Object.keys(results));
			console.info(results.expires);
			console.info(results.cert);
			console.info(results.chain);
			console.info();
			console.info();
		}
	}

	// Try EC + RSA
	var rnd = random();
	happyPath('EC', 'RSA', rnd)
		.then(function() {
			console.info('PASS: ECDSA account key with RSA server key');
			// Now try RSA + EC
			rnd = random();
			return happyPath('RSA', 'EC', rnd).then(function() {
				console.info('PASS: RSA account key with ECDSA server key');
			});
		})
		.then(function() {
			console.info('PASS');
		})
		.catch(function(err) {
			console.error('Error:');
			console.error(err.stack);
		});

	function randomDomains(rnd) {
		return ['foo-acmejs', 'bar-acmejs', '*.baz-acmejs', 'baz-acmejs'].map(
			function(pre) {
				return punycode.toASCII(pre + '-' + rnd + '.' + config.domain);
			}
		);
	}

	function random() {
		return (
			parseInt(
				Math.random()
					.toString()
					.slice(2, 99),
				10
			)
				.toString(16)
				.slice(0, 4) + 'δΎ‹'
		);
	}
};