import {
	IConnections,
	ILogger,
	INode,
	IRun,
	LoggerProxy,
	Workflow,
} from 'n8n-workflow';

import {
	createDeferredPromise,
	WorkflowExecute,
} from '../src';

import * as Helpers from './Helpers';


describe('WorkflowExecute', () => {

	describe('run', () => {

		const tests: Array<{
			description: string;
			input: {
				workflowData: {
					nodes: INode[],
					connections: IConnections,
				}
			},
			output: {
				nodeExecutionOrder: string[];
				nodeData: {
					[key: string]: any[][]; // tslint:disable-line:no-any
				};
			},
		}> = [
			{
				description: 'should run basic two node workflow',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									100,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value1",
												"value": 1,
											},
										],
									},
								},
								"name": "Set",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									280,
									300,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set',
					],
					nodeData: {
						Set: [
							[
								{
									value1: 1,
								},
							],
						],
					},
				},
			},
			{
				description: 'should run node twice when it has two input connections',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									100,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value1",
												"value": 1,
											},
										],
									},
								},
								"name": "Set1",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									300,
									250,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value2",
												"value": 2,
											},
										],
									},
								},
								"name": "Set2",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									500,
									400,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set1",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Set2",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set1": {
								"main": [
									[
										{
											"node": "Set2",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set1',
						'Set2',
						'Set2',
					],
					nodeData: {
						Set1: [
							[
								{
									value1: 1,
								},
							],
						],
						Set2: [
							[
								{
									value2: 2,
								},
							],
							[
								{
									value1: 1,
									value2: 2,
								},
							],
						],
					},
				},
			},
			{
				description: 'should run complicated multi node workflow',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {
									"mode": "passThrough",
								},
								"name": "Merge4",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1150,
									500,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value2",
												"value": 2,
											},
										],
									},
								},
								"name": "Set2",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									290,
									400,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value4",
												"value": 4,
											},
										],
									},
								},
								"name": "Set4",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									850,
									200,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value3",
												"value": 3,
											},
										],
									},
								},
								"name": "Set3",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									650,
									200,
								],
							},
							{
								"parameters": {
									"mode": "passThrough",
								},
								"name": "Merge4",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1150,
									500,
								],
							},
							{
								"parameters": {},
								"name": "Merge3",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1000,
									400,
								],
							},
							{
								"parameters": {
									"mode": "passThrough",
									"output": "input2",
								},
								"name": "Merge2",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									700,
									400,
								],
							},
							{
								"parameters": {},
								"name": "Merge1",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									500,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value1",
												"value": 1,
											},
										],
									},
								},
								"name": "Set1",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									300,
									200,
								],
							},
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									100,
									300,
								],
							},
						],
						"connections": {
							"Set2": {
								"main": [
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 1,
										},
										{
											"node": "Merge2",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"Set4": {
								"main": [
									[
										{
											"node": "Merge3",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set3": {
								"main": [
									[
										{
											"node": "Set4",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Merge3": {
								"main": [
									[
										{
											"node": "Merge4",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Merge2": {
								"main": [
									[
										{
											"node": "Merge3",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"Merge1": {
								"main": [
									[
										{
											"node": "Merge2",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set1": {
								"main": [
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Set3",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Start": {
								"main": [
									[
										{
											"node": "Set1",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Set2",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Merge4",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set1',
						'Set2',
						'Set3',
						'Merge1',
						'Set4',
						'Merge2',
						'Merge3',
						'Merge4',
					],
					nodeData: {
						Set1: [
							[
								{
									value1: 1,
								},
							],
						],
						Set2: [
							[
								{
									value2: 2,
								},
							],
						],
						Set3: [
							[
								{
									value1: 1,
									value3: 3,
								},
							],
						],
						Set4: [
							[
								{
									value1: 1,
									value3: 3,
									value4: 4,
								},
							],
						],
						Merge1: [
							[
								{
									value1: 1,
								},
								{
									value2: 2,
								},
							],
						],
						Merge2: [
							[
								{
									value2: 2,
								},
							],
						],
						Merge3: [
							[
								{
									value1: 1,
									value3: 3,
									value4: 4,
								},
								{
									value2: 2,
								},
							],
						],
						Merge4: [
							[
								{
									value1: 1,
									value3: 3,
									value4: 4,
								},
								{
									value2: 2,
								},
							],
						],
					},
				},
			},
			{
				description: 'should run workflow also if node has multiple input connections and one is empty',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									250,
									450,
								],
							},
							{
								"parameters": {
									"conditions": {
										"boolean": [],
										"number": [
											{
												"value1": "={{Object.keys($json).length}}",
												"operation": "notEqual",
											},
										],
									},
								},
								"name": "IF",
								"type": "n8n-nodes-base.if",
								"typeVersion": 1,
								"position": [
									650,
									350,
								],
							},
							{
								"parameters": {},
								"name": "Merge1",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1150,
									450,
								],
							},
							{
								"parameters": {
									"values": {
										"string": [
											{
												"name": "test1",
												"value": "a",
											},
										],
									},
									"options": {},
								},
								"name": "Set1",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									450,
								],
							},
							{
								"parameters": {
									"values": {
										"string": [
											{
												"name": "test2",
												"value": "b",
											},
										],
									},
									"options": {},
								},
								"name": "Set2",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									800,
									250,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set1",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"IF": {
								"main": [
									[
										{
											"node": "Set2",
											"type": "main",
											"index": 0,
										},
									],
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set1": {
								"main": [
									[
										{
											"node": "IF",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Merge1",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"Set2": {
								"main": [
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set1',
						'IF',
						'Set2',
						'Merge1',
					],
					nodeData: {
						Merge1: [
							[
								{
									test1: 'a',
									test2: 'b',
								},
								{
									test1: 'a',
								},
							],
						],
					},
				},
			},
			{
				description: 'should use empty data if second input does not have any data',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									250,
									300,
								],
							},
							{
								"parameters": {},
								"name": "Merge",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									800,
									450,
								],
							},
							{
								"parameters": {},
								"name": "Merge1",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1000,
									300,
								],
							},
							{
								"parameters": {
									"conditions": {
										"boolean": [
											{
												"value2": true,
											},
										],
										"string": [
											{
												"value1": "={{$json[\"key\"]}}",
												"value2": "a",
											},
										],
									},
									"combineOperation": "any",
								},
								"name": "IF",
								"type": "n8n-nodes-base.if",
								"typeVersion": 1,
								"position": [
									600,
									600,
								],
								"alwaysOutputData": false,
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "number0",
											},
										],
										"string": [
											{
												"name": "key",
												"value": "a",
											},
										],
									},
									"options": {},
								},
								"name": "Set0",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "number1",
												"value": 1,
											},
										],
										"string": [
											{
												"name": "key",
												"value": "b",
											},
										],
									},
									"options": {},
								},
								"name": "Set1",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									450,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "number2",
												"value": 2,
											},
										],
										"string": [
											{
												"name": "key",
												"value": "c",
											},
										],
									},
									"options": {},
								},
								"name": "Set2",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									600,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set0",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Merge": {
								"main": [
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"IF": {
								"main": [
									[
										{
											"node": "Merge",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"Set0": {
								"main": [
									[
										{
											"node": "Merge1",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set1": {
								"main": [
									[
										{
											"node": "Merge",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set2": {
								"main": [
									[
										{
											"node": "IF",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set0',
						'Set2',
						'IF',
						'Set1',
						'Merge',
						'Merge1',
					],
					nodeData: {
						Merge: [
							[
								{
									number1: 1,
									key: "b",
								},
							],
						],
						Merge1: [
							[
								{
									number0: 0,
									key: "a",
								},
								{
									number1: 1,
									key: "b",
								},
							],
						],
					},
				},
			},
			{
				description: 'should use empty data if input of sibling does not receive any data from parent',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									250,
									300,
								],
							},
							{
								"parameters": {
									"conditions": {
										"number": [
											{
												"value1": "={{$json[\"value1\"]}}",
												"operation": "equal",
												"value2": 1,
											},
										],
									},
								},
								"name": "IF",
								"type": "n8n-nodes-base.if",
								"typeVersion": 1,
								"position": [
									650,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"string": [],
										"number": [
											{
												"name": "value2",
												"value": 2,
											},
										],
									},
									"options": {},
								},
								"name": "Set2",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									850,
									450,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value1",
												"value": 1,
											},
										],
									},
									"options": {},
								},
								"name": "Set1",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									300,
								],
							},
							{
								"parameters": {},
								"name": "Merge",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1050,
									300,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set1",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"IF": {
								"main": [
									[
										{
											"node": "Merge",
											"type": "main",
											"index": 0,
										},
									],
									[
										{
											"node": "Set2",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set2": {
								"main": [
									[
										{
											"node": "Merge",
											"type": "main",
											"index": 1,
										},
									],
								],
							},
							"Set1": {
								"main": [
									[
										{
											"node": "IF",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set1',
						'IF',
						'Set2',
						'Merge',
					],
					nodeData: {
						Merge: [
							[
								{
									value1: 1,
								},
								{
									value2: 2,
								},
							],
						],
					},
				},
			},
			{
				description: 'should not use empty data in sibling if parent did not send any data',
				input: {
					// Leave the workflowData in regular JSON to be able to easily
					// copy it from/in the UI
					workflowData: {
						"nodes": [
							{
								"parameters": {},
								"name": "Start",
								"type": "n8n-nodes-base.start",
								"typeVersion": 1,
								"position": [
									250,
									300,
								],
							},
							{
								"parameters": {
									"values": {
										"number": [
											{
												"name": "value1",
											},
										],
									},
									"options": {},
								},
								"name": "Set",
								"type": "n8n-nodes-base.set",
								"typeVersion": 1,
								"position": [
									450,
									300,
								],
							},
							{
								"parameters": {},
								"name": "Merge",
								"type": "n8n-nodes-base.merge",
								"typeVersion": 1,
								"position": [
									1050,
									250,
								],
							},
							{
								"parameters": {
									"conditions": {
										"number": [
											{
												"value1": "={{$json[\"value1\"]}}",
												"operation": "equal",
												"value2": 1,
											},
										],
									},
								},
								"name": "IF",
								"type": "n8n-nodes-base.if",
								"typeVersion": 1,
								"position": [
									650,
									300,
								],
							},
							{
								"parameters": {},
								"name": "NoOpTrue",
								"type": "n8n-nodes-base.noOp",
								"typeVersion": 1,
								"position": [
									850,
									150,
								],
							},
							{
								"parameters": {},
								"name": "NoOpFalse",
								"type": "n8n-nodes-base.noOp",
								"typeVersion": 1,
								"position": [
									850,
									400,
								],
							},
						],
						"connections": {
							"Start": {
								"main": [
									[
										{
											"node": "Set",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"Set": {
								"main": [
									[
										{
											"node": "IF",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"IF": {
								"main": [
									[
										{
											"node": "NoOpTrue",
											"type": "main",
											"index": 0,
										},
										{
											"node": "Merge",
											"type": "main",
											"index": 1,
										},
									],
									[
										{
											"node": "NoOpFalse",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
							"NoOpTrue": {
								"main": [
									[
										{
											"node": "Merge",
											"type": "main",
											"index": 0,
										},
									],
								],
							},
						},
					},
				},
				output: {
					nodeExecutionOrder: [
						'Start',
						'Set',
						'IF',
						'NoOpFalse',
					],
					nodeData: {
						IF: [
							[],
						],
						NoOpFalse: [
							[
								{
									value1: 0,
								},
							],
						],
					},
				},
			},
		];

		const fakeLogger = {
			log: () => {},
			debug:  () => {},
			verbose:  () => {},
			info:  () => {},
			warn:  () => {},
			error:  () => {},
		} as ILogger;

		const executionMode = 'manual';
		const nodeTypes = Helpers.NodeTypes();
		LoggerProxy.init(fakeLogger);

		for (const testData of tests) {
			test(testData.description, async () => {

				const workflowInstance = new Workflow({ id: 'test', nodes: testData.input.workflowData.nodes, connections: testData.input.workflowData.connections, active: false, nodeTypes });

				const waitPromise = await createDeferredPromise<IRun>();
				const nodeExecutionOrder: string[] = [];
				const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);

				const workflowExecute = new WorkflowExecute(additionalData, executionMode);

				const executionData = await workflowExecute.run(workflowInstance, undefined);

				const result = await waitPromise.promise();

				// Check if the data from WorkflowExecute is identical to data received
				// by the webhooks
				expect(executionData).toEqual(result);

				// Check if the output data of the nodes is correct
				for (const nodeName of Object.keys(testData.output.nodeData)) {
					if (result.data.resultData.runData[nodeName] === undefined) {
						throw new Error(`Data for node "${nodeName}" is missing!`);
					}

					const resultData = result.data.resultData.runData[nodeName].map((nodeData) => {
						if (nodeData.data === undefined) {
							return null;
						}
						return nodeData.data.main[0]!.map((entry) => entry.json );
					});

					// expect(resultData).toEqual(testData.output.nodeData[nodeName]);
					expect(resultData).toEqual(testData.output.nodeData[nodeName]);
				}

				// Check if the nodes did execute in the correct order
				expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);

				// Check if other data has correct value
				expect(result.finished).toEqual(true);
				expect(result.data.executionData!.contextData).toEqual({});
				expect(result.data.executionData!.nodeExecutionStack).toEqual([]);
			});
		}

	});

});