import fs from 'fs';

import {
	SCHEDULE_TRIGGER_NODE_NAME,
	CODE_NODE_NAME,
	CODE_NODE_DISPLAY_NAME,
	EDIT_FIELDS_SET_NODE_NAME,
	MANUAL_TRIGGER_NODE_NAME,
	MANUAL_TRIGGER_NODE_DISPLAY_NAME,
} from '../../../../../config/constants';
import { test, expect } from '../../../../../fixtures/base';
import { resolveFromRoot } from '../../../../../utils/path-helper';

test.describe(
	'Undo/Redo',
	{
		annotation: [{ type: 'owner', description: 'Adore' }],
	},
	() => {
		test.beforeEach(async ({ n8n }) => {
			await n8n.start.fromBlankCanvas();
		});

		test('should undo/redo deleting node using context menu', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.canvas.clickZoomToFitButton();

			await n8n.canvas.deleteNodeFromContextMenu(CODE_NODE_DISPLAY_NAME);
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
		});

		test('should undo/redo deleting node using keyboard shortcut', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.canvas.nodeByName(CODE_NODE_DISPLAY_NAME).click();
			await n8n.canvas.clickZoomToFitButton();
			await n8n.page.keyboard.press('Backspace');

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
		});

		test('should undo/redo deleting node between two connected nodes', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.canvas.addNode(EDIT_FIELDS_SET_NODE_NAME, { closeNDV: true });
			await n8n.canvas.nodeByName(CODE_NODE_DISPLAY_NAME).click();
			await n8n.canvas.clickZoomToFitButton();
			await n8n.page.keyboard.press('Backspace');

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(2);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
		});

		test('should undo/redo deleting whole workflow', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.page.keyboard.press('Escape');
			await n8n.page.keyboard.press('Escape');

			await n8n.canvas.hitDeleteAllNodes();

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(0);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(0);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
		});

		test('should undo/redo moving nodes', async ({ n8n }) => {
			await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
			await n8n.canvas.nodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();

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

			await n8n.canvas.clickZoomToFitButton();

			const codeNodeName = CODE_NODE_DISPLAY_NAME;

			const initialPosition = await n8n.canvas.getNodePosition(codeNodeName);

			await n8n.canvas.dragNodeToRelativePosition(codeNodeName, 50, 150);

			const newPosition = await n8n.canvas.getNodePosition(codeNodeName);
			expect(newPosition.x).toBeGreaterThan(initialPosition.x);
			expect(newPosition.y).toBeGreaterThan(initialPosition.y);

			await n8n.canvas.hitUndo();
			const undoPosition = await n8n.canvas.getNodePosition(codeNodeName);
			expect(undoPosition.x).toBeCloseTo(initialPosition.x, 1);
			expect(undoPosition.y).toBeCloseTo(initialPosition.y, 1);

			await n8n.canvas.hitRedo();
			const redoPosition = await n8n.canvas.getNodePosition(codeNodeName);
			expect(redoPosition.x).toBeGreaterThan(initialPosition.x);
			expect(redoPosition.y).toBeGreaterThan(initialPosition.y);
		});

		test('should undo/redo deleting a connection using context menu', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });

			await n8n.canvas.deleteConnectionBetweenNodes(
				SCHEDULE_TRIGGER_NODE_NAME,
				CODE_NODE_DISPLAY_NAME,
			);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.nodeConnections()).toHaveCount(0);
		});

		test('should undo/redo disabling a node using context menu', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });

			await n8n.canvas.disableNodeFromContextMenu(CODE_NODE_DISPLAY_NAME);
			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(0);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
		});

		test('should undo/redo disabling a node using keyboard shortcut', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.canvas.getCanvasNodes().last().click();
			await n8n.page.keyboard.press('d');

			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(0);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
		});

		test('should undo/redo disabling multiple nodes', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });
			await n8n.page.keyboard.press('Escape');
			await n8n.page.keyboard.press('Escape');

			await n8n.canvas.selectAll();
			await n8n.page.keyboard.press('d');

			await expect(n8n.canvas.disabledNodes()).toHaveCount(2);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(0);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.disabledNodes()).toHaveCount(2);
		});

		test('should undo/redo duplicating a node', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true });

			await n8n.canvas.duplicateNode(CODE_NODE_DISPLAY_NAME);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
		});

		test('should undo/redo pasting nodes', async ({ n8n }) => {
			const workflowJson = fs.readFileSync(
				resolveFromRoot('workflows', 'Test_workflow-actions_paste-data.json'),
				'utf-8',
			);

			await n8n.canvas.canvasPane().click();
			await n8n.clipboard.paste(workflowJson);
			await n8n.canvas.clickZoomToFitButton();

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(0);

			await n8n.canvas.hitRedo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5);
		});

		test('should be able to copy and paste pinned data nodes in workflows with dynamic Switch node', async ({
			n8n,
		}) => {
			const workflowJson = fs.readFileSync(
				resolveFromRoot('workflows', 'Test_workflow_form_switch.json'),
				'utf-8',
			);

			await n8n.canvas.canvasPane().click();
			await n8n.clipboard.paste(workflowJson);
			await n8n.canvas.clickZoomToFitButton();

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
			await expect(n8n.canvas.getNodeInputHandles('Switch')).toHaveCount(1);

			// Wait for clipboard paste throttling
			await n8n.page.waitForTimeout(1000);

			await n8n.canvas.canvasPane().click();
			await n8n.clipboard.paste(workflowJson);

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(4);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(2);

			await n8n.canvas.hitUndo();

			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(2);
			await expect(n8n.canvas.nodeConnections()).toHaveCount(1);
			await expect(n8n.canvas.getNodeInputHandles('Switch')).toHaveCount(1);
		});

		test('should not undo/redo when NDV or a modal is open', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await n8n.ndv.clickBackToCanvasButton();

			await n8n.sideBar.clickAboutMenuItem();
			await expect(n8n.sideBar.getAboutModal()).toBeVisible();
			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);
			await n8n.sideBar.closeAboutModal();

			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(0);
		});

		test('should not undo/redo when NDV or a prompt is open', async ({ n8n }) => {
			await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME, { closeNDV: true });
			await n8n.canvas.clickWorkflowMenu();
			await n8n.canvas.clickImportFromURL();

			await n8n.canvas.getImportURLInput().click();
			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(1);

			await n8n.canvas.clickCancelImportURL();
			await n8n.canvas.hitUndo();
			await expect(n8n.canvas.getCanvasNodes()).toHaveCount(0);
		});
	},
);
