import {fork} from 'child_process';
import test from 'ava';
import getStream from 'get-stream';
import delay from 'delay';
import execa from 'execa';

// Slow things down for reliable tests on Travis CI
const tick = time => delay(process.env.CI ? time * 10 : time);

test.beforeEach.cb(t => {
	const child = fork('fixture.js', {silent: true});

	const exit = new Promise((resolve, reject) =>
		child.on('exit', exitCode =>
			(exitCode > 0 ? reject : resolve)(exitCode)
		)
	);

	t.context = {
		// Tell the child to create a promise, and reject it
		rejectWithError: (key, message) => child.send({
			action: 'reject-error',
			key,
			message
		}),
		rejectWithValue: (key, value) => child.send({
			action: 'reject-value',
			key,
			value
		}),
		rejectWithNothing: key => child.send({
			action: 'reject-nothing',
			key
		}),

		// Tell the child to handle the promise previously rejected
		handle: key => child.send({
			action: 'handle',
			key
		}),

		// Tell the child to reinstall loudRejection
		reinstall: () => child.send({action: 'reinstall'}),

		// Kill the child (returns a promise for when the child is done)
		kill: () => {
			child.kill();
			return exit;
		},

		// The stdout of the child. Useful for debug
		stdout: getStream(child.stdout),

		// The stderr of the child. This is where unhandledRejections will be logged
		stderr: getStream(child.stderr),

		// Promise for when the child has exited
		exit
	};

	child.on('message', message => {
		if (message.status !== 'ready') {
			t.fail(`I got a message I don't understand: ${JSON.stringify(message)}`);
		}

		t.end();
	});
});

test('no rejections', async t => {
	const child = t.context;

	await tick(20);
	await child.kill();

	t.is(await child.stderr, '');
});

test('one unhandled rejection', async t => {
	const child = t.context;

	child.rejectWithError('a', 'foo123');
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /foo123/);
});

test('two unhandled rejections', async t => {
	const child = t.context;

	child.rejectWithError('a', 'foo456');
	child.rejectWithError('b', 'bar789');
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /foo456/);
	t.regex(await child.stderr, /bar789/);
});

test('one rejection that is handled before exit', async t => {
	const child = t.context;

	child.rejectWithError('a', 'foo123');
	await tick(20);
	child.handle('a');
	await tick(20);
	await child.kill();

	t.is(await child.stderr, '');
});

test('two rejections, first one handled', async t => {
	const child = t.context;

	child.rejectWithError('a', 'foo987');
	child.rejectWithError('b', 'bar654');
	await tick(20);
	child.handle('a');
	await tick(20);
	await child.kill();

	t.false(/foo987/.test(await child.stderr));
	t.regex(await child.stderr, /bar654/);
});

test('two rejections, last one handled', async t => {
	const child = t.context;

	child.rejectWithError('a', 'foo987');
	child.rejectWithError('b', 'bar654');
	await tick(20);
	child.handle('b');
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /foo987/);
	t.false(/bar654/.test(await child.stderr));
});

test('rejection with a string value', async t => {
	const child = t.context;

	child.rejectWithValue('a', 'foo123');
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /Promise rejected with value: 'foo123'/);
});

test('rejection with a falsy value', async t => {
	const child = t.context;

	child.rejectWithValue('a', false);
	child.rejectWithValue('a', 0);
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /Promise rejected with value: false/);
	t.regex(await child.stderr, /Promise rejected with value: 0/);
});

test('rejection with no value', async t => {
	const child = t.context;

	child.rejectWithNothing();
	await tick(20);
	await child.kill();

	t.regex(await child.stderr, /Promise rejected with value: undefined/);
});

test('custom log function', async t => {
	// TODO: use execa `reject: false` option
	const stdout = await execa('node', ['fixture-custom-log.js']).catch(error => error.stdout);
	t.is(stdout.split('\n')[0], 'custom-log Error: foo');
});
