import { nanoid } from 'nanoid';

import {
	CODE_NODE_DISPLAY_NAME,
	CODE_NODE_NAME,
	MANUAL_TRIGGER_NODE_NAME,
} from '../../../../../config/constants';
import { test, expect } from '../../../../../fixtures/base';

test.describe(
	'Code node',
	{
		annotation: [{ type: 'owner', description: 'NODES' }],
	},
	() => {
		test.describe('Code editor', () => {
			test.beforeEach(async ({ n8n }) => {
				await n8n.start.fromBlankCanvas();
				await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
				await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });
			});

			test('should show correct placeholders switching modes', async ({ n8n }) => {
				await expect(
					n8n.ndv.getPlaceholderText('// Loop over input items and add a new field'),
				).toBeVisible();

				await n8n.ndv.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');

				await expect(
					n8n.ndv.getPlaceholderText("// Add a new field called 'myNewField'"),
				).toBeVisible();

				await n8n.ndv.selectOptionInParameterDropdown('mode', 'Run Once for All Items');

				await expect(
					n8n.ndv.getPlaceholderText('// Loop over input items and add a new field'),
				).toBeVisible();
			});

			test('should execute the placeholder successfully in both modes', async ({ n8n }) => {
				await n8n.ndv.execute();

				await expect(
					n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
				).toBeVisible();

				await n8n.ndv.selectOptionInParameterDropdown('mode', 'Run Once for Each Item');

				await n8n.ndv.execute();

				await expect(
					n8n.notifications.getNotificationByTitle('Node executed successfully').first(),
				).toBeVisible();
			});

			test('should allow switching between sibling code nodes', async ({ n8n }) => {
				await n8n.ndv.getCodeEditor().fill("console.log('Code in JavaScript1')");
				await n8n.ndv.close();

				await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });

				await n8n.ndv.getCodeEditor().fill("console.log('Code in JavaScript2')");
				await n8n.ndv.close();

				await n8n.canvas.openNode(CODE_NODE_DISPLAY_NAME);

				await n8n.ndv.clickFloatingNode(CODE_NODE_DISPLAY_NAME + '1');
				await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('Code in JavaScript2')");

				await n8n.ndv.clickFloatingNode(CODE_NODE_DISPLAY_NAME);
				await expect(n8n.ndv.getCodeEditor()).toContainText("console.log('Code in JavaScript1')");
			});

			test('should show lint errors in `runOnceForAllItems` mode', async ({ n8n }) => {
				await n8n.ndv.getCodeEditor().fill(`$input.itemMatching()
$input.item
$('When clicking ‘Execute workflow’').item
$input.first(1)

for (const item of $input.all()) {
  item.foo
}

return
`);
				await expect(n8n.ndv.getLintErrors()).toHaveCount(6);
				const firstLintError = n8n.ndv.getLintErrors().first();
				await expect(firstLintError).toBeVisible();
				await firstLintError.hover({ force: true });

				// Wait for lint tooltip to appear after hover
				await expect(n8n.ndv.getLintTooltip()).toBeVisible({ timeout: 5000 });
				await expect(n8n.ndv.getLintTooltip()).toContainText(
					'`.itemMatching()` expects an item index to be passed in as its argument.',
				);
			});
		});

		test.describe
			.serial('Run Once for Each Item', () => {
				test('should show lint errors in `runOnceForEachItem` mode', async ({ n8n }) => {
					await n8n.start.fromBlankCanvas();
					await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
					await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });
					await n8n.ndv.toggleCodeMode('Run Once for Each Item');

					await n8n.ndv.getCodeEditor().fill(`$input.itemMatching()
$input.all()
$input.first()
$input.item()

return []
`);
					// Verify lint errors are detected (tooltip hover tested in runOnceForAllItems test)
					await expect(n8n.ndv.getLintErrors()).toHaveCount(7);
				});
			});

		test.describe('Ask AI', () => {
			test.describe('Enabled', () => {
				test.beforeEach(async ({ api, n8n }) => {
					await api.enableFeature('askAi');
					await n8n.start.fromBlankCanvas();
					await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
					await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript' });
				});

				test('tab should exist if experiment selected and be selectable', async ({ n8n }) => {
					await n8n.ndv.clickAskAiTab();
					await expect(n8n.ndv.getAskAiTabPanel()).toBeVisible();
					await expect(n8n.ndv.getHeyAiText()).toBeVisible();
				});

				test('generate code button should have correct state & tooltips', async ({ n8n }) => {
					await n8n.ndv.clickAskAiTab();
					await expect(n8n.ndv.getAskAiTabPanel()).toBeVisible();

					await expect(n8n.ndv.getAskAiCtaButton()).toBeDisabled();
					await n8n.ndv.getAskAiCtaButton().hover();
					await expect(n8n.ndv.getAskAiCtaTooltipNoInputData()).toBeVisible();

					await n8n.ndv.executePrevious();
					await n8n.ndv.getAskAiCtaButton().hover();
					await expect(n8n.ndv.getAskAiCtaTooltipNoPrompt()).toBeVisible();

					await n8n.ndv.getAskAiPromptInput().fill(nanoid(14));

					await n8n.ndv.getAskAiCtaButton().hover();
					await expect(n8n.ndv.getAskAiCtaTooltipPromptTooShort()).toBeVisible();

					await n8n.ndv.getAskAiPromptInput().fill(nanoid(15));
					await expect(n8n.ndv.getAskAiCtaButton()).toBeEnabled();

					await expect(n8n.ndv.getAskAiPromptCounter()).toContainText('15 / 600');
				});

				test('should send correct schema and replace code', async ({ n8n }) => {
					const prompt = nanoid(20);
					await n8n.ndv.clickAskAiTab();
					await n8n.ndv.executePrevious();

					await n8n.ndv.getAskAiPromptInput().fill(prompt);

					await n8n.page.route('**/rest/ai/ask-ai', async (route) => {
						await route.fulfill({
							status: 200,
							contentType: 'application/json',
							body: JSON.stringify({
								data: {
									code: 'console.log("Hello World")',
								},
							}),
						});
					});

					const [request] = await Promise.all([
						n8n.page.waitForRequest('**/rest/ai/ask-ai'),
						n8n.ndv.getAskAiCtaButton().click(),
					]);

					const requestBody = request.postDataJSON();
					expect(requestBody).toHaveProperty('question');
					expect(requestBody).toHaveProperty('context');
					expect(requestBody).toHaveProperty('forNode');
					expect(requestBody.context).toHaveProperty('schema');
					expect(requestBody.context).toHaveProperty('ndvPushRef');
					expect(requestBody.context).toHaveProperty('pushRef');
					expect(requestBody.context).toHaveProperty('inputSchema');

					await expect(n8n.ndv.getCodeGenerationCompletedText()).toBeVisible();
					await expect(n8n.ndv.getCodeTabPanel()).toContainText('console.log("Hello World")');
					await expect(n8n.ndv.getCodeTab()).toHaveClass(/is-active/);
				});

				const handledCodes = [
					{ code: 400, message: 'Code generation failed due to an unknown reason' },
					{ code: 413, message: 'Your workflow data is too large for AI to process' },
					{ code: 429, message: "We've hit our rate limit with our AI partner" },
					{
						code: 500,
						message:
							'Code generation failed with error: Request failed with status code 500. Try again in a few minutes',
					},
				];

				handledCodes.forEach(({ code, message }) => {
					test(`should show error based on status code ${code}`, async ({ n8n }) => {
						const prompt = nanoid(20);
						await n8n.ndv.clickAskAiTab();
						await n8n.ndv.executePrevious();

						await n8n.ndv.getAskAiPromptInput().fill(prompt);

						await n8n.page.route('**/rest/ai/ask-ai', async (route) => {
							await route.fulfill({
								status: code,
							});
						});

						await n8n.ndv.getAskAiCtaButton().click();
						await expect(n8n.ndv.getErrorMessageText(message)).toBeVisible();
					});
				});
			});
		});
	},
);
