import { expect } from 'chai';
import { Command } from 'commander';
import { SinonStubbedInstance, stub } from 'sinon';
import { assertConjunctionParameters, CLICommand, CommandDescriptor } from './cli_command';

import {
	DriveNameParameter,
	UnsafeDrivePasswordParameter,
	SeedPhraseParameter,
	WalletFileParameter
} from '../parameter_declarations';
import { CliApiObject } from './cli';
import { baseArgv } from './test_constants';
import { Parameter } from './parameter';
import { CLIAction } from './action';
import { SUCCESS_EXIT_CODE } from './error_codes';

const MY_DRIVE_NAME = 'My awesome drive!';
const testingCommandName = 'drive-name-test';
async function dummyAction() {
	return SUCCESS_EXIT_CODE;
}
const driveNameCommandDescription: CommandDescriptor = {
	name: testingCommandName,
	parameters: [DriveNameParameter],
	action: new CLIAction(dummyAction)
};
const driveNameArgv: string[] = [...baseArgv, testingCommandName, '--drive-name', MY_DRIVE_NAME];
const nonEmptyValue = 'non-empty value';
const commandDescriptorRequiredWallet: CommandDescriptor = {
	name: testingCommandName,
	parameters: [
		WalletFileParameter,
		{ name: UnsafeDrivePasswordParameter, requiredConjunctionParameters: [WalletFileParameter] }
	],
	action: new CLIAction(dummyAction)
};
const parsedOptionsMissingWallet = {
	[WalletFileParameter]: undefined,
	[UnsafeDrivePasswordParameter]: nonEmptyValue
};
const commandDescriptorForbiddenWalletFileAndSeedPhrase: CommandDescriptor = {
	name: testingCommandName,
	parameters: [WalletFileParameter, SeedPhraseParameter],
	action: new CLIAction(dummyAction)
};
const parsedCommandOptionsBothSpecified = {
	[WalletFileParameter]: nonEmptyValue,
	[SeedPhraseParameter]: nonEmptyValue
};

class TestCliApiObject {
	constructor(private readonly program: CliApiObject = new Command() as CliApiObject) {}
	arguments = stub(this.program, 'arguments').returnsThis();
	action = stub(this.program, 'action').returnsThis();
	option = stub(this.program, 'option').returnsThis();
	requiredOption = stub(this.program, 'requiredOption').returnsThis();
	command = stub(this.program, 'command').returnsThis();
	parse = stub(this.program, 'parse');
	addHelpCommand = stub(this.program, 'addHelpCommand').returnsThis();
	opts = stub(this.program, 'opts').returnsThis();
	exitOverride = stub(this.program, 'exitOverride').returnsThis();
	name = stub(this.program, 'name').returnsThis();
	usage = stub(this.program, 'usage').returnsThis();
	outputHelp = stub(this.program, 'outputHelp');
}

describe('CLICommand class', () => {
	let stubbedProgram: SinonStubbedInstance<CliApiObject>;
	const program: CliApiObject = new Command() as CliApiObject;

	before(() => {
		stubbedProgram = new TestCliApiObject(program);
	});

	it('Calls the library API function once when a command is set', () => {
		new CLICommand(driveNameCommandDescription, stubbedProgram);
		expect(stubbedProgram.command.calledOnce).to.be.true;
		expect(stubbedProgram.action.calledOnce).to.be.true;
	});

	it('The library parses the given argv', () => {
		CLICommand.parse(stubbedProgram, driveNameArgv);
		expect(stubbedProgram.parse.calledOnce).to.be.true;
	});

	it('Assert required in conjunction parameters', () => {
		expect(function () {
			assertConjunctionParameters(commandDescriptorRequiredWallet, parsedOptionsMissingWallet);
		}).to.throw();
	});

	it('Assert forbidden in conjunction parameters', () => {
		expect(function () {
			assertConjunctionParameters(
				commandDescriptorForbiddenWalletFileAndSeedPhrase,
				parsedCommandOptionsBothSpecified
			);
		}).to.throw();
	});

	it('No colliding parameters', () => {
		const allCommandDescriptors = CLICommand.getAllCommandDescriptors();
		allCommandDescriptors.forEach((command) => {
			const parameters = command.parameters.map((param) => new Parameter(param));
			parameters.forEach((parameter_1, index) => {
				const allParametersExceptMe = parameters;
				allParametersExceptMe.splice(index);
				const collidingParameters = allParametersExceptMe.filter((parameter_2) => {
					const areAllowedInConjunction = !parameter_2.forbiddenParametersInConjunction.includes(
						parameter_1.name
					);
					if (areAllowedInConjunction) {
						return parameter_2.aliases.find((alias) => parameter_1.aliases.includes(alias));
					}
					return false;
				});
				// if (collidingParameters.length) {
				// 	debugger;
				// }
				expect(collidingParameters).to.be.empty;
			});
		});
	});
});