/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { deepStrictEqual, strictEqual } from 'assert';
import 'mocha';
import { basename } from 'path';
import { asArray, getCompletionItemsFromSpecs, getCurrentCommandAndArgs } from '../terminalSuggestMain';
import { getTokenType } from '../tokens';
import { cdTestSuiteSpec as cdTestSuite } from './completions/cd.test';
import { codeSpecOptionsAndSubcommands, codeTestSuite, codeTunnelTestSuite } from './completions/code.test';
import { testPaths, type ISuiteSpec } from './helpers';
import { codeInsidersTestSuite, codeTunnelInsidersTestSuite } from './completions/code-insiders.test';
import { lsTestSuiteSpec } from './completions/upstream/ls.test';
import { echoTestSuiteSpec } from './completions/upstream/echo.test';
import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test';
import { rmTestSuiteSpec } from './completions/upstream/rm.test';
import { rmdirTestSuiteSpec } from './completions/upstream/rmdir.test';
import { touchTestSuiteSpec } from './completions/upstream/touch.test';
import { gitTestSuiteSpec } from './completions/upstream/git.test';
import { osIsWindows } from '../helpers/os';
import codeCompletionSpec from '../completions/code';
import { figGenericTestSuites } from './fig.test';
import { IFigExecuteExternals } from '../fig/execute';

const testSpecs2: ISuiteSpec[] = [
	{
		name: 'Fallback to default completions',
		completionSpecs: [],
		availableCommands: [],
		testSpecs: [
			{ input: '|', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: '|.', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: '|./', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: 'fakecommand |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
		]
	},

	...figGenericTestSuites,

	// completions/
	cdTestSuite,
	codeTestSuite,
	codeInsidersTestSuite,
	codeTunnelTestSuite,
	codeTunnelInsidersTestSuite,

	// completions/upstream/
	echoTestSuiteSpec,
	lsTestSuiteSpec,
	mkdirTestSuiteSpec,
	rmTestSuiteSpec,
	rmdirTestSuiteSpec,
	touchTestSuiteSpec,
	gitTestSuiteSpec,
];

if (osIsWindows()) {
	testSpecs2.push({
		name: 'Handle options extensions on Windows',
		completionSpecs: [codeCompletionSpec],
		availableCommands: [
			'code.bat',
			'code.cmd',
			'code.exe',
			'code.anything',
		],
		testSpecs: [
			{ input: 'code |', expectedCompletions: codeSpecOptionsAndSubcommands, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: 'code.bat |', expectedCompletions: codeSpecOptionsAndSubcommands, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: 'code.cmd |', expectedCompletions: codeSpecOptionsAndSubcommands, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: 'code.exe |', expectedCompletions: codeSpecOptionsAndSubcommands, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
			{ input: 'code.anything |', expectedCompletions: codeSpecOptionsAndSubcommands, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } },
		]
	});
}

suite('Terminal Suggest', () => {
	for (const suiteSpec of testSpecs2) {
		suite(suiteSpec.name, () => {
			const completionSpecs = asArray(suiteSpec.completionSpecs);
			const availableCommands = asArray(suiteSpec.availableCommands);
			for (const testSpec of suiteSpec.testSpecs) {
				let expectedString = testSpec.expectedCompletions ? `[${testSpec.expectedCompletions.map(e => `'${e}'`).join(', ')}]` : '[]';
				if (testSpec.expectedResourceRequests) {
					expectedString += ` + ${testSpec.expectedResourceRequests.type}`;
					if (testSpec.expectedResourceRequests.cwd && testSpec.expectedResourceRequests.cwd.fsPath !== testPaths.cwd.fsPath) {
						expectedString += ` @ ${basename(testSpec.expectedResourceRequests.cwd.fsPath)}/`;
					}
				}
				test(`'${testSpec.input}' -> ${expectedString}`, async () => {
					const commandLine = testSpec.input.split('|')[0];
					const cursorIndex = testSpec.input.indexOf('|');
					const currentCommandString = getCurrentCommandAndArgs(commandLine, cursorIndex, undefined);
					const showFiles = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both';
					const showDirectories = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both';
					const terminalContext = { commandLine, cursorIndex };
					const result = await getCompletionItemsFromSpecs(
						completionSpecs,
						terminalContext,
						availableCommands.map(c => { return { label: c }; }),
						currentCommandString,
						getTokenType(terminalContext, undefined),
						testPaths.cwd,
						{},
						'testName',
						undefined,
						new MockFigExecuteExternals()
					);
					deepStrictEqual(
						// Add detail to the label if it exists
						result.items.map(i => {
							if (typeof i.label === 'object' && i.label.detail) {
								return `${i.label.label}${i.label.detail}`;
							}
							return i.label;
						}).sort(),
						(testSpec.expectedCompletions ?? []).sort()
					);
					strictEqual(result.showFiles, showFiles, 'Show files different than expected, got: ' + result.showFiles);
					strictEqual(result.showDirectories, showDirectories, 'Show directories different than expected, got: ' + result.showDirectories);
					if (testSpec.expectedResourceRequests?.cwd) {
						strictEqual(result.cwd?.fsPath, testSpec.expectedResourceRequests.cwd.fsPath, 'Non matching cwd');
					}
				});
			}
		});
	}
});


class MockFigExecuteExternals implements IFigExecuteExternals {
	public async executeCommand(input: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> {
		return this.executeCommandTimeout(input);
	}
	async executeCommandTimeout(input: Fig.ExecuteCommandInput): Promise<Fig.ExecuteCommandOutput> {
		const command = [input.command, ...input.args].join(' ');
		try {
			return {
				status: 0,
				stdout: input.command,
				stderr: '',
			};
		} catch (err) {
			console.error(`Error running shell command '${command}'`, { err });
			throw err;
		}
	}
}
