const http2 = require('http2');
const tls = require('tls');
const {promisify} = require('util');
const test = require('ava');
const pem = require('pem');
const resolveALPN = require('.');

const createCertificate = promisify(pem.createCertificate);

const createServer = async () => {
	const caKeys = await createCertificate({
		days: 1,
		selfSigned: true
	});

	const caRootKey = caKeys.serviceKey;
	const caRootCert = caKeys.certificate;

	const keys = await createCertificate({
		serviceCertificate: caRootCert,
		serviceKey: caRootKey,
		serial: Date.now(),
		days: 500,
		country: '',
		state: '',
		locality: '',
		organization: '',
		organizationUnit: '',
		commonName: 'localhost'
	});

	const key = keys.clientKey;
	const cert = keys.certificate;

	const s = http2.createSecureServer({cert, key, allowHTTP1: true});

	s.listen = promisify(s.listen);
	s.close = promisify(s.close);

	s.options = {
		host: 'localhost',
		rejectUnauthorized: false,
		ALPNProtocols: ['h2']
	};

	s.on('listening', () => {
		s.options.port = s.address().port;
	});

	return s;
};

let s;

test.before('setup', async () => {
	s = await createServer();
	await s.listen();
});

test.after('cleanup', async () => {
	await s.close();
});

test('works', async t => {
	const result = await resolveALPN(s.options);
	t.deepEqual(result, {
		alpnProtocol: 'h2',
		timeout: false
	});
});

test('`resolveSocket` option', async t => {
	const result = await resolveALPN({
		...s.options,
		resolveSocket: true
	});

	t.is(result.alpnProtocol, 'h2');
	t.true(result.socket instanceof tls.TLSSocket);

	result.socket.destroy();
});

test('empty options', async t => {
	const error = await t.throwsAsync(() => resolveALPN());
	const {code} = error;

	t.true(code === 'ECONNREFUSED' || code === 'ERR_MISSING_ARGS' || code === 'EADDRNOTAVAIL', error.stack);
});

test('works with timeout', async t => {
	t.timeout(100);

	const {socket, timeout} = await resolveALPN({
		host: '123.123.123.123',
		port: 443,
		ALPNProtocols: ['h2'],
		timeout: 1,
		resolveSocket: true
	});

	await new Promise((resolve, reject) => {
		socket.once('error', error => {
			reject(error);
			t.fail(error);
		});

		socket.once('timeout', resolve);
	});

	socket.destroy();

	t.true(timeout);
});

test('accept custom createConnection function', async t => {
	const custom = Symbol('custom');

	const result = await resolveALPN({
		...s.options,
		resolveSocket: true
	}, (options, callback) => {
		const socket = tls.connect(options, callback);
		socket[custom] = true;

		return socket;
	});

	t.is(result.alpnProtocol, 'h2');
	t.true(result.socket instanceof tls.TLSSocket);
	t.true(result.socket[custom]);

	result.socket.destroy();
});

test('async createConnection function', async t => {
	const custom = Symbol('custom');

	const result = await resolveALPN({
		...s.options,
		resolveSocket: true
	}, async (options, callback) => {
		return new Promise((resolve, reject) => {
			const socket = tls.connect(options, callback);
			socket[custom] = true;

			socket.once('error', reject);
			socket.once('connect', () => {
				socket.off('error', reject);
				resolve(socket);
			});
		});
	});

	t.is(result.alpnProtocol, 'h2');
	t.true(result.socket instanceof tls.TLSSocket);
	t.true(result.socket[custom]);

	result.socket.destroy();
});

test('waits for promise to be resolved', async t => {
	const custom = Symbol('custom');

	const result = await resolveALPN({
		...s.options,
		resolveSocket: true
	}, async (options, callback) => {
		const socket = tls.connect(options, callback);
		socket[custom] = true;

		await new Promise(resolve => {
			socket.once('secureConnect', resolve);
		});

		return socket;
	});

	t.is(result.alpnProtocol, 'h2');
	t.true(result.socket instanceof tls.TLSSocket);
	t.true(result.socket[custom]);

	t.is(result.socket.listenerCount('error'), 0);

	result.socket.destroy();
});
