import execa from 'execa';

// Allow to override used bins for testing purposes
const GIT = process.env.TRAVIS_COMMITLINT_GIT_BIN || 'git';
const COMMITLINT =
	process.env.TRAVIS_COMMITLINT_BIN || require('@commitlint/cli');

const REQUIRED = [
	'TRAVIS_COMMIT',
	'TRAVIS_COMMIT_RANGE',
	'TRAVIS_EVENT_TYPE',
	'TRAVIS_REPO_SLUG',
	'TRAVIS_PULL_REQUEST_SLUG',
];

const COMMIT = process.env.TRAVIS_COMMIT || '';
const REPO_SLUG = process.env.TRAVIS_REPO_SLUG;
const PR_SLUG = process.env.TRAVIS_PULL_REQUEST_SLUG || REPO_SLUG;
const RANGE = process.env.TRAVIS_COMMIT_RANGE;
const IS_PR = process.env.TRAVIS_EVENT_TYPE === 'pull_request';

main().catch((err) => {
	console.log(err);
	process.exit(1);
});

async function main() {
	validate();

	// Stash changes in working copy if needed
	const pop = await stash();

	// Make base and source available as dedicated remotes
	await Promise.all([
		() => fetch({name: 'base', url: `https://github.com/${REPO_SLUG}.git`}),
		IS_PR
			? () => fetch({name: 'source', url: `https://github.com/${PR_SLUG}.git`})
			: () => Promise.resolve(),
	]);

	// Restore stashed changes if any
	await pop();

	const args = process.argv.slice(2);

	// Lint all commits in TRAVIS_COMMIT_RANGE if available
	if (IS_PR && RANGE) {
		const [start, end] = RANGE.split('.').filter(Boolean);
		await lint(['--from', start, '--to', end, ...args]);
	} else {
		const input = await log(COMMIT);
		await lint(args, {input});
	}
}

async function git(args: string[], options: execa.Options = {}) {
	return execa(GIT, args, {
		stdio: 'inherit',
		...options,
	});
}

async function fetch({name, url}: {name: string; url: string}) {
	await git(['remote', 'add', name, url]);
	await git(['fetch', name, '--quiet']);
}

async function isClean() {
	const result = await git(['status', '--porcelain'], {
		stdio: ['pipe', 'pipe', 'pipe'],
	});
	return !(result.stdout && result.stdout.trim());
}

async function lint(args: string[], options: execa.Options = {}) {
	return execa(COMMITLINT, args, {
		stdio: ['pipe', 'inherit', 'inherit'],
		...options,
	});
}

async function log(hash: string) {
	const result = await git(['log', '-n', '1', '--pretty=format:%B', hash], {
		stdio: 'pipe',
	});
	return result.stdout;
}

async function stash() {
	if (await isClean()) {
		return () => Promise.resolve();
	}
	await git(['stash', '-k', '-u', '--quiet']);
	return () => git(['stash', 'pop', '--quiet']);
}

function validate() {
	if (process.env.CI !== 'true' || process.env.TRAVIS !== 'true') {
		throw new Error(
			`@commitlint/travis-cli is intended to be used on Travis CI`
		);
	}

	const missing = REQUIRED.filter((envVar) => !(envVar in process.env));

	if (missing.length > 0) {
		const stanza = missing.length > 1 ? 'they were not' : 'it was not';
		throw new Error(
			`Expected ${missing.join(', ')} to be defined globally, ${stanza}.`
		);
	}
}