import type { MessageItem } from 'vscode';
import { ConfigurationTarget, ThemeIcon, window } from 'vscode';
import type { BlameIgnoreRevsFileError, GitCommandContext } from '@gitlens/git/errors.js';
import { BlameIgnoreRevsFileBadRevisionError, GitCommandError } from '@gitlens/git/errors.js';
import type { GitCommit } from '@gitlens/git/models/commit.js';
import { filterMap } from '@gitlens/utils/array.js';
import { Logger } from '@gitlens/utils/logger.js';
import type { SuppressedMessages } from './config.js';
import { urls } from './constants.js';
import type { Source } from './constants.telemetry.js';
import type { Container } from './container.js';
import { formatIdentityDisplayName, getCommitFormattedDate } from './git/utils/-webview/commit.utils.js';
import { mcpRegistrationAllowed } from './plus/gk/utils/-webview/mcp.utils.js';
import { executeCommand, executeCoreCommand } from './system/-webview/command.js';
import { configuration } from './system/-webview/configuration.js';
import { openUrl } from './system/-webview/vscode/uris.js';

export function showBlameInvalidIgnoreRevsFileWarningMessage(
	ex: BlameIgnoreRevsFileError | BlameIgnoreRevsFileBadRevisionError,
): Promise<MessageItem | undefined> {
	if (ex instanceof BlameIgnoreRevsFileBadRevisionError) {
		return showMessage(
			'error',
			`Unable to show blame. Invalid revision (${ex.revision}) specified in the blame.ignoreRevsFile in your Git config.`,
			'suppressBlameInvalidIgnoreRevsFileBadRevisionWarning',
		);
	}

	return showMessage(
		'error',
		`Unable to show blame. Invalid or missing blame.ignoreRevsFile (${ex.fileName}) specified in your Git config.`,
		'suppressBlameInvalidIgnoreRevsFileWarning',
	);
}

export function showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise<MessageItem | undefined> {
	if (commit == null) {
		return showMessage('info', 'There is no previous commit.', 'suppressCommitHasNoPreviousCommitWarning');
	}
	return showMessage(
		'info',
		`Commit ${commit.shortSha} (${formatIdentityDisplayName(commit.author)}, ${getCommitFormattedDate(commit)}) has no previous commit.`,
		'suppressCommitHasNoPreviousCommitWarning',
	);
}

export function showCommitNotFoundWarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage('warn', `${message}. The commit could not be found.`, 'suppressCommitNotFoundWarning');
}

export async function showCreatePullRequestPrompt(branch: string): Promise<boolean> {
	const create = { title: 'Create Pull Request...' };
	const result = await showMessage(
		'info',
		`Would you like to create a Pull Request for branch '${branch}'?`,
		'suppressCreatePullRequestPrompt',
		{ title: "Don't Show Again" },
		create,
	);
	return result === create;
}

export async function showDebugLoggingWarningMessage(): Promise<boolean> {
	const disable = { title: 'Disable Debug Logging' };
	const result = await showMessage(
		'warn',
		'GitLens debug logging is currently enabled. Unless you are reporting an issue, it is recommended to be disabled. Would you like to disable it?',
		'suppressDebugLoggingWarning',
		{ title: "Don't Show Again" },
		disable,
	);

	return result === disable;
}

export async function showGenericErrorMessage(message: string): Promise<void> {
	if (Logger.enabled('error')) {
		const result = await showMessage('error', `${message}. See output channel for more details.`, undefined, null, {
			title: 'Open Output Channel',
		});

		if (result != null) {
			Logger.showOutputChannel();
		}
	} else {
		const result = await showMessage(
			'error',
			`${message}. If the error persists, please enable debug logging and try again.`,
			undefined,
			null,
			{
				title: 'Enable Debug Logging',
			},
		);

		if (result != null) {
			void executeCommand('gitlens.enableDebugLogging');
		}
	}
}

function escapeShellArg(arg: string): string {
	// If the argument contains spaces, quotes, or special characters, wrap it in single quotes
	// and escape any single quotes within it
	if (/[\s"'`$\\|&;<>(){}[\]!*?#~]/.test(arg)) {
		// Escape single quotes by replacing ' with '\''
		return `'${arg.replace(/'/g, "'\\''")}'`;
	}
	return arg;
}

function showGitCommandInTerminal(gitCommand: GitCommandContext, error: GitCommandError<any>): void {
	const terminal = window.createTerminal({
		cwd: gitCommand.repoPath,
		name: 'GitLens',
		hideFromUser: false,
		iconPath: new ThemeIcon('gitlens-gitlens'),
		isTransient: true,
		message: `\x1b[1mGitLens attempted to run this Git command and it failed:\x1b[0m\r\n\x1b[31m${error.message}\x1b[0m\r\n\x1b[3mYou can run it again or modify it to diagnose the issue.\x1b[0m\r\n`,
	});
	const command = `git ${filterMap(gitCommand.args, a => (a != null ? escapeShellArg(a) : undefined)).join(' ')}`;
	terminal.sendText(command, false);
	terminal.show();
}

export async function showGitErrorMessage(error: Error | GitCommandError<any>, message?: string): Promise<void> {
	if (!GitCommandError.is(error)) {
		return void showGenericErrorMessage(message ?? error.message);
	}

	const { gitCommand } = error.details;
	message = message ?? error.message;
	const loggingEnabled = Logger.enabled('error');

	const openOutputChannelOrEnableLogging: MessageItem = {
		title: loggingEnabled ? 'Open Output Channel' : 'Enable Debug Logging',
	};
	const openInTerminalAction: MessageItem = { title: 'Open in Terminal' };

	const result = await showMessage(
		'error',
		`${message.endsWith('.') ? message : `${message}.`} ${loggingEnabled ? 'See output channel for more details.' : 'If the error persists, please enable debug logging and try again.'}`,
		undefined,
		null,
		...(gitCommand != null
			? [openInTerminalAction, openOutputChannelOrEnableLogging]
			: [openOutputChannelOrEnableLogging]),
	);

	if (result === openInTerminalAction) {
		showGitCommandInTerminal(gitCommand, error);
		return;
	}

	if (result === openOutputChannelOrEnableLogging) {
		if (loggingEnabled) {
			Logger.showOutputChannel();
		} else {
			void executeCommand('gitlens.enableDebugLogging');
		}
	}
}

export async function showBitbucketPRCommitLinksAppNotInstalledWarningMessage(revLink: string): Promise<void> {
	const allowAccess = { title: 'Allow Access' };
	const result = await showMessage(
		'warn',
		`GitLens cannot access Bitbucket PRs for commits.
		Allow access by visiting [this commit](${revLink}) on Bitbucket and click “Pull requests” under the “Apps” section on the bottom right
		or [read our docs](https://help.gitkraken.com/gitlens/gitlens-troubleshooting/#enable-showing-bitbucket-pull-request-for-a-commit) for more info.`,
		'suppressBitbucketPRCommitLinksAppNotInstalledWarning',
		{ title: "Don't Show Again" },
		allowAccess,
	);
	if (result === allowAccess) {
		void openUrl(revLink);
	}
}

export function showFileNotUnderSourceControlWarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage(
		'warn',
		`${message}. The file is probably not under source control.`,
		'suppressFileNotUnderSourceControlWarning',
	);
}

export function showGitDisabledErrorMessage(): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		'GitLens requires Git to be enabled. Please re-enable Git \u2014 set `git.enabled` to true and reload.',
		'suppressGitDisabledWarning',
	);
}

export function showGitInvalidConfigErrorMessage(): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		'GitLens is unable to use Git. Your Git configuration seems to be invalid. Please resolve any issues with your Git configuration and reload.',
	);
}

export function showGitMissingErrorMessage(): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		"GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that 'git.path' is pointed to its installed location.",
		'suppressGitMissingWarning',
	);
}

export function showGitVersionUnsupportedErrorMessage(
	version: string,
	required: string,
): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		`GitLens requires a newer version of Git (>= ${required}) than is currently installed (${version}). Please install a more recent version of Git.`,
		'suppressGitVersionWarning',
	);
}

export async function showPreReleaseExpiredErrorMessage(version: string): Promise<void> {
	const upgrade = { title: 'Upgrade' };
	const switchToRelease = { title: 'Switch to Release Version' };
	const result = await showMessage(
		'error',
		`This pre-release version (${version}) of GitLens has expired. Please upgrade to a more recent pre-release, or switch to the release version.`,
		undefined,
		null,
		upgrade,
		switchToRelease,
	);

	if (result === upgrade) {
		void executeCoreCommand('workbench.extensions.installExtension', 'eamodio.gitlens', {
			installPreReleaseVersion: true,
		});
		void executeCoreCommand('workbench.extensions.action.extensionUpdates');
	} else if (result === switchToRelease) {
		void executeCoreCommand('workbench.extensions.action.installExtensions');
		void executeCoreCommand('workbench.extensions.action.switchToRelease', 'eamodio.gitlens');
	}
}

export function showLineUncommittedWarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage('warn', `${message}. The line has uncommitted changes.`, 'suppressLineUncommittedWarning');
}

export function showNoRepositoryWarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage('warn', `${message}. No repository could be found.`, 'suppressNoRepositoryWarning');
}

export function showGkDisconnectedTooManyFailedRequestsWarningMessage(): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		`Requests to GitKraken have stopped being sent for this session, because of too many failed requests.`,
		'suppressGkDisconnectedTooManyFailedRequestsWarningMessage',
		undefined,
		{
			title: 'OK',
		},
	);
}

export function showGkRequestFailed500WarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage('error', message, 'suppressGkRequestFailed500Warning', undefined, {
		title: 'OK',
	});
}

export function showGkRequestTimedOutWarningMessage(): Promise<MessageItem | undefined> {
	return showMessage('error', `GitKraken request timed out.`, 'suppressGkRequestTimedOutWarning', undefined, {
		title: 'OK',
	});
}

export function showIntegrationDisconnectedTooManyFailedRequestsWarningMessage(
	providerName: string,
): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		`Rich integration with ${providerName} has been disconnected for this session, because of too many failed requests.`,
		'suppressIntegrationDisconnectedTooManyFailedRequestsWarning',
		undefined,
		{
			title: 'OK',
		},
	);
}

export function showIntegrationRequestFailed500WarningMessage(message: string): Promise<MessageItem | undefined> {
	return showMessage('error', message, 'suppressIntegrationRequestFailed500Warning', undefined, {
		title: 'OK',
	});
}

export function showIntegrationRequestTimedOutWarningMessage(providerName: string): Promise<MessageItem | undefined> {
	return showMessage(
		'error',
		`${providerName} request timed out.`,
		'suppressIntegrationRequestTimedOutWarning',
		undefined,
		{
			title: 'OK',
		},
	);
}

export async function showWhatsNewMessage(majorVersion: string): Promise<void> {
	const confirm = { title: 'OK', isCloseAffordance: true };
	const releaseNotes = { title: 'View Release Notes' };
	const result = await showMessage(
		'info',
		`GitLens upgraded to ${majorVersion}${
			majorVersion === '17'
				? ' with the all new [GitKraken AI](https://gitkraken.com/solutions/gitkraken-ai?source=gitlens&product=gitlens&utm_source=gitlens-extension&utm_medium=in-app-links) access included in GitLens Pro, AI changelog and pull request creation, and Bitbucket integration.'
				: " — see what's new."
		}`,
		undefined,
		null,
		releaseNotes,
		confirm,
	);

	if (result === releaseNotes) {
		void openUrl(urls.releaseNotes);
	}
}

export async function showMcpMessage(container: Container, _current: string): Promise<void> {
	const isAutoInstallable = mcpRegistrationAllowed(container);
	const confirm = { title: 'OK', isCloseAffordance: true };
	const learnMore = { title: 'Learn More' };
	const connectMore = { title: 'Connect More Agents' };
	const install = { title: 'Install GitKraken MCP' };

	let result: MessageItem | undefined;
	if (isAutoInstallable) {
		result = await showMessage(
			'info',
			`GitLens adds the GitKraken MCP into your AI chat, leveraging Git and your integrations to provide context and perform actions. You can also connect MCP to other agents on your machine.`,
			undefined,
			null,
			connectMore,
			learnMore,
			confirm,
		);
	} else {
		result = await showMessage(
			'info',
			`Allow GitLens to add the GitKraken MCP into your AI chat, leveraging Git and your integrations (issues, PRs, etc) to provide context and perform actions. Saving you time and context switching.`,
			undefined,
			null,
			install,
			learnMore,
			confirm,
		);
	}

	if (result === install) {
		void executeCommand<Source>('gitlens.ai.mcp.install', { source: 'mcp-welcome-message' });
	}

	if (result === connectMore) {
		void executeCommand<Source>('gitlens.ai.mcp.selectAgents', { source: 'mcp-welcome-message' });
	}

	if (result === learnMore) {
		void openUrl(urls.helpCenterMCP);
	}
}

export async function showCursorMcpCleanupMessage(): Promise<void> {
	const learnMore = { title: 'Learn More' };
	const confirm = { title: 'OK', isCloseAffordance: true };

	const result = await showMessage(
		'info',
		`GitLens now registers the GitKraken MCP automatically in Cursor. You may have a duplicate entry in your Cursor \`mcp.json\` — remove \`mcpServers.GitKraken\` to clean it up.`,
		undefined,
		null,
		learnMore,
		confirm,
	);

	if (result === learnMore) {
		void openUrl(urls.helpCenterMCP);
	}
}

export async function showMessage(
	type: 'info' | 'warn' | 'error',
	message: string,
	suppressionKey?: SuppressedMessages,
	dontShowAgain: MessageItem | null = { title: "Don't Show Again" },
	...actions: MessageItem[]
): Promise<MessageItem | undefined> {
	Logger.debug(`ShowMessage(${type}, '${message}', ${suppressionKey}, ${JSON.stringify(dontShowAgain)})`);

	if (suppressionKey != null && configuration.get(`advanced.messages.${suppressionKey}` as const)) {
		Logger.debug(`ShowMessage(${type}, '${message}', ${suppressionKey}, ${JSON.stringify(dontShowAgain)}) skipped`);
		return undefined;
	}

	if (suppressionKey != null && dontShowAgain !== null) {
		actions.push(dontShowAgain);
	}

	let result: MessageItem | undefined = undefined;
	switch (type) {
		case 'info':
			result = await window.showInformationMessage(message, ...actions);
			break;

		case 'warn':
			result = await window.showWarningMessage(message, ...actions);
			break;

		case 'error':
			result = await window.showErrorMessage(message, ...actions);
			break;
	}

	if (suppressionKey != null && (dontShowAgain === null || result === dontShowAgain)) {
		Logger.debug(
			`ShowMessage(${type}, '${message}', ${suppressionKey}, ${JSON.stringify(
				dontShowAgain,
			)}) don't show again requested`,
		);
		await suppressedMessage(suppressionKey);

		if (result === dontShowAgain) return undefined;
	}

	Logger.debug(
		`ShowMessage(${type}, '${message}', ${suppressionKey}, ${JSON.stringify(dontShowAgain)}) returned ${
			result != null ? result.title : result
		}`,
	);
	return result;
}

function suppressedMessage(suppressionKey: SuppressedMessages) {
	const messages = { ...configuration.get('advanced.messages') };

	messages[suppressionKey] = true;

	for (const [key, value] of Object.entries(messages)) {
		if (value !== true) {
			// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
			delete messages[key as keyof typeof messages];
		}
	}

	return configuration.update('advanced.messages', messages, ConfigurationTarget.Global);
}
