import { test, expect } from '../../../../fixtures/base';
import executionOutOfMemoryResponse from '../../../../fixtures/execution-out-of-memory-server-response.json';
import { retryUntil } from '../../../../utils/retry-utils';

test.describe('Executions Filter', () => {
	test.beforeEach(async ({ n8n }) => {
		await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
	});

	// There is flakiness in this test. When the dropdown is opened, at times it appears to automatically close even without user interaction.
	// Manual timeouts have been added to try to mitigate this, but it still happens.
	test.fixme(
		'should keep popover open when selecting from dropdown inside it @fixme',
		async ({ n8n }) => {
			// Regression test: Element Plus dropdowns are teleported to body, causing
			// Reka UI's DismissableLayer to detect clicks as "outside" and close the popover.
			// This test verifies the popover stays open during and after dropdown selection.

			// Create some executions first
			await n8n.executionsComposer.createExecutions(2);

			// Go to executions tab
			await n8n.canvas.clickExecutionsTab();
			await expect(n8n.executions.getExecutionItems().first()).toBeVisible();

			// Open filter popover
			await n8n.executions.openFilter();
			const filterForm = n8n.executions.getFilterForm();
			await n8n.page.waitForTimeout(1500);
			await expect(filterForm).toBeVisible();

			// Click to open the status dropdown
			await n8n.executions.getStatusSelect().click();
			// Verify popover is still open while dropdown is open
			await expect(filterForm).toBeVisible();

			// Set up listener for the filtered executions request
			const filterRequestPromise = n8n.page.waitForRequest(
				(request) =>
					request.url().includes('/rest/executions?filter=') && request.url().includes('success'),
			);

			await n8n.page.waitForTimeout(500);
			// Select an option from the dropdown
			await n8n.page.getByRole('option', { name: 'Success' }).click();

			// Verify the filter request was sent to the backend (confirms selection worked)
			const filterRequest = await filterRequestPromise;
			expect(filterRequest.url()).toContain('status');
			expect(filterRequest.url()).toContain('success');

			// KEY ASSERTION: Verify the popover did NOT close after selecting from dropdown
			await expect(filterForm).toBeVisible();
		},
	);
});

const ERROR_MESSAGES = {
	OUT_OF_MEMORY: 'Workflow did not finish, possible out-of-memory issue',
} as const;

const TIMEOUTS = {
	EXECUTIONS_REFRESH_INTERVAL: 4000,
} as const;

test.describe('Workflow Executions', () => {
	test.describe('when workflow is saved', () => {
		test.beforeEach(async ({ n8n }) => {
			await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
		});

		test('should render executions tab correctly', async ({ n8n }) => {
			await n8n.executionsComposer.createExecutions(15);

			await n8n.canvas.toggleNodeEnabled('Error');
			await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
			await n8n.executionsComposer.createExecutions(2);

			await n8n.canvas.toggleNodeEnabled('Error');
			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
			await n8n.executionsComposer.createExecutions(15);

			const executionsResponsePromise = n8n.page.waitForResponse((response) =>
				response.url().includes('/rest/executions?filter='),
			);

			await n8n.canvas.clickExecutionsTab();
			await executionsResponsePromise;

			await expect(n8n.executions.getExecutionItems().first()).toBeVisible();

			const loadMoreResponsePromise = n8n.page.waitForResponse((response) =>
				response.url().includes('/rest/executions?filter='),
			);
			await n8n.executions.scrollExecutionsListToBottom();
			await loadMoreResponsePromise;

			await expect(n8n.executions.getExecutionItems()).toHaveCount(30, { timeout: 15000 });
			await expect(n8n.executions.getSuccessfulExecutionItems()).toHaveCount(28);
			await expect(n8n.executions.getFailedExecutionItems()).toHaveCount(2);
			await expect(n8n.executions.getFirstExecutionItem()).toHaveClass(/_active_/);
		});

		test('should not redirect back to execution tab when request is not done before leaving the page', async ({
			n8n,
		}) => {
			await n8n.page.route('**/rest/executions?filter=*', (route) => route.continue());
			await n8n.page.route('**/rest/executions/active?filter=*', (route) => route.continue());

			await n8n.canvas.clickExecutionsTab();
			await n8n.canvas.clickEditorTab();
			await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
				timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
			});

			for (let i = 0; i < 3; i++) {
				await n8n.canvas.clickExecutionsTab();
				await n8n.canvas.clickEditorTab();
			}
			await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
				timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
			});

			await n8n.canvas.clickExecutionsTab();
			// eslint-disable-next-line playwright/no-wait-for-timeout
			await n8n.page.waitForTimeout(1000);
			await n8n.canvas.clickEditorTab();
			await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
				timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
			});
		});

		test('should not redirect back to execution tab when slow request is not done before leaving the page', async ({
			n8n,
		}) => {
			await n8n.page.route('**/rest/executions?filter=*', async (route) => {
				await new Promise((resolve) => setTimeout(resolve, 2000));
				await route.continue();
			});

			await n8n.page.route('**/rest/executions/active?filter=*', async (route) => {
				await new Promise((resolve) => setTimeout(resolve, 2000));
				await route.continue();
			});

			await n8n.canvas.clickExecutionsTab();
			await n8n.page.waitForURL(/\/executions/);
			await n8n.canvas.clickEditorTab();
			await n8n.page.waitForURL(/\/workflow\/[^/]+$/);

			await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
				timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
			});
		});

		test('should error toast when server error message returned without stack trace', async ({
			n8n,
		}) => {
			const responsePromise = n8n.page.waitForResponse(
				(response) =>
					response.url().includes('/rest/workflows/') &&
					response.url().includes('/run') &&
					response.request().method() === 'POST',
			);
			await n8n.canvas.clickExecuteWorkflowButton();
			await responsePromise;

			await n8n.page.route('**/rest/executions/*', async (route) => {
				if (route.request().url().includes('?filter=')) {
					await route.continue();
					return;
				}

				await route.fulfill({
					status: 200,
					contentType: 'application/json',
					body: JSON.stringify(executionOutOfMemoryResponse),
				});
			});

			const executionDetailPromise = n8n.page.waitForResponse(
				(response) =>
					response.url().includes('/rest/executions/') && !response.url().includes('?filter='),
			);
			await n8n.canvas.clickExecutionsTab();
			await executionDetailPromise;

			const iframe = n8n.executions.getPreviewIframe();
			await expect(iframe.locator('body')).not.toBeEmpty();

			await n8n.executions.getErrorNotificationsInPreview().first().waitFor({ timeout: 5000 });

			const errorNotification = n8n.executions
				.getErrorNotificationsInPreview()
				.filter({ hasText: ERROR_MESSAGES.OUT_OF_MEMORY });
			await expect(errorNotification).toBeVisible();
		});

		test.fixme(
			'should show workflow data in executions tab after hard reload and modify name and tags @fixme',
			async () => {
				// TODO: Migrate from Cypress
			},
		);
		test.fixme('should load items and auto scroll after filter change @fixme', async () => {
			// TODO: This should be a component test
		});

		test('should redirect back to editor after seeing a couple of execution using browser back button', async ({
			n8n,
		}) => {
			await n8n.executionsComposer.createExecutions(15);

			await n8n.canvas.toggleNodeEnabled('Error');
			await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
			await n8n.executionsComposer.createExecutions(2);

			await n8n.canvas.toggleNodeEnabled('Error');
			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
			await n8n.executionsComposer.createExecutions(15);

			const executionsResponsePromise = n8n.page.waitForResponse((response) =>
				response.url().includes('/rest/executions?filter='),
			);

			await n8n.canvas.clickExecutionsTab();
			await executionsResponsePromise;

			const iframe = n8n.executions.getPreviewIframe();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.executions.getExecutionItems().nth(2).click();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.executions.getExecutionItems().nth(4).click();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.executions.getExecutionItems().nth(6).click();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.page.goBack();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.page.goBack();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.page.goBack();
			await expect(iframe.locator('body')).toBeAttached();

			await n8n.page.goBack();

			await expect(n8n.page).not.toHaveURL(/\/executions/);
			await expect(n8n.page).toHaveURL(/\/workflow\//);
			await expect(n8n.canvas.canvasPane()).toBeVisible();
		});
	});

	test.describe('execution timing', () => {
		test('should preserve execution start time for standard workflow', async ({ api }) => {
			const { webhookPath, workflowId, createdWorkflow } =
				await api.workflows.importWorkflowFromFile('simple-webhook-test.json');

			await api.workflows.activate(workflowId, createdWorkflow.versionId!);

			const webhookResponse = await api.request.post(`/webhook/${webhookPath}`, { data: {} });
			expect(webhookResponse.ok()).toBe(true);

			const execution = await api.workflows.waitForExecution(workflowId, 10000);
			const originalStartedAt = execution.startedAt;
			console.log('originalStartedAt', originalStartedAt);

			const finalExecution = await api.workflows.getExecution(execution.id);
			console.log('finalExecution', finalExecution);
			expect(finalExecution.startedAt).toBe(originalStartedAt);
		});

		test('should preserve execution start time after resuming from wait node', async ({ api }) => {
			const { webhookPath, workflowId, createdWorkflow } =
				await api.workflows.importWorkflowFromFile('cat-1854-wait-execution-history.json');

			await api.workflows.activate(workflowId, createdWorkflow.versionId!);

			const webhookResponse = await api.request.get(`/webhook/${webhookPath}`);
			expect(webhookResponse.ok()).toBe(true);

			const execution = await api.workflows.waitForWorkflowStatus(workflowId, 'waiting', 10000);
			const originalStartedAt = execution.startedAt;

			await new Promise((resolve) => setTimeout(resolve, 1000));

			const resumeResponse = await api.request.get(`/webhook-waiting/${execution.id}`);
			expect(resumeResponse.ok()).toBe(true);

			await api.workflows.waitForExecution(workflowId, 15000);

			await retryUntil(async () => {
				const finalExecution = await api.workflows.getExecution(execution.id);
				expect(finalExecution.startedAt).toBe(originalStartedAt);
			});
		});
	});

	test.describe('when new workflow is not saved', () => {
		test.beforeEach(async ({ n8n }) => {
			await n8n.start.fromBlankCanvas();
		});

		test('should open executions tab', async ({ n8n }) => {
			await n8n.canvas.clickExecutionsTab();
			await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
			await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
			await expect(n8n.page.getByTestId('workflow-execution-no-trigger-content')).toBeVisible();

			await n8n.page.getByRole('button', { name: 'Add first step' }).click();
			await n8n.canvas.nodeCreatorItemByName('Trigger manually').click();

			await n8n.canvas.clickExecutionsTab();
			await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
			await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
			await expect(n8n.page.getByTestId('workflow-execution-no-content')).toBeVisible();

			await n8n.canvas.waitForSaveWorkflowCompleted();
			await n8n.page.waitForURL(/\/workflow\/.+\/executions$/);
		});
	});
});
