/**
 * Tests for the actual WorkflowExecute.runNode method
 * These tests ensure the real implementation behavior is preserved
 * during refactoring
 * They have been generated by claude code, but have been proven to work
 * via test coverage reports and mutation testing.
 */

// Vitest mocks invoked via `new` reject arrow functions ("is not a constructor").
// Wrap arrow implementations so the mock returns the same instance the test built.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ctor = <T extends object, A extends any[]>(impl: (...args: A) => T) =>
	function (this: T, ...args: A) {
		return impl(...args);
	};

// Mock all external dependencies first, before any imports
vi.mock('@n8n/config', async (importActual) => ({
	...(await importActual()),
	GlobalConfig: vi.fn().mockImplementation(() => ({
		sentry: { backendDsn: '' },
	})),
}));

vi.mock('@n8n/di', () => ({
	Container: {
		get: vi.fn(),
	},
	Service: () => (target: unknown) => target,
}));

vi.mock('@/errors/error-reporter', () => ({
	ErrorReporter() {
		return {
			error: vi.fn(),
		};
	},
}));

vi.mock('../node-execution-context', async (importActual) => {
	return {
		...(await importActual()),
		ExecuteContext: vi.fn().mockImplementation(function (this: { hints: unknown[] }) {
			this.hints = [];
		}),
		PollContext: vi.fn().mockImplementation(function () {}),
	};
});

vi.mock('../triggers-and-pollers', () => ({
	TriggersAndPollers: vi.fn(),
}));

vi.mock('../routing-node', () => ({
	RoutingNode: vi.fn().mockImplementation(function (this: { runNode: Mock }) {
		this.runNode = vi.fn().mockResolvedValue([[{ json: { routed: 'result' } }]]);
	}),
}));

vi.mock('@/node-execute-functions', () => ({
	getExecuteTriggerFunctions: vi.fn(),
}));

vi.mock('../../utils/convert-binary-data.ts', () => ({
	convertBinaryData: vi.fn(),
}));

// Now import the real classes
import { GlobalConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import type {
	ExecutionBaseError,
	IExecuteData,
	INode,
	INodeType,
	IRunExecutionData,
	ITaskDataConnections,
	IWorkflowExecuteAdditionalData,
	Workflow,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError, Node, createRunExecutionData } from 'n8n-workflow';
import type { Mock, Mocked, MockedClass } from 'vitest';
import { mock } from 'vitest-mock-extended';

import { ExecuteContext, PollContext } from '../node-execution-context';
import { RoutingNode } from '../routing-node';
import { TriggersAndPollers } from '../triggers-and-pollers';
import { WorkflowExecute } from '../workflow-execute';

const mockContainer = Container as Mocked<typeof Container>;
const mockExecuteContext = ExecuteContext as MockedClass<typeof ExecuteContext>;
const mockPollContext = PollContext as MockedClass<typeof PollContext>;
const mockRoutingNode = RoutingNode as MockedClass<typeof RoutingNode>;

describe('WorkflowExecute.runNode - Real Implementation', () => {
	let workflowExecute: WorkflowExecute;
	let mockWorkflow: Mocked<Workflow>;
	let mockAdditionalData: Mocked<IWorkflowExecuteAdditionalData>;
	let mockRunExecutionData: IRunExecutionData;
	let mockNode: INode;
	let mockNodeType: Mocked<INodeType>;
	let mockExecutionData: IExecuteData;

	beforeEach(() => {
		vi.clearAllMocks();

		// Setup Container mock for different dependencies
		const mockTriggersAndPollersInstance = {
			runTrigger: vi.fn(),
		};
		const mockGlobalConfigInstance = {
			sentry: { backendDsn: '' },
		};
		mockContainer.get.mockImplementation((token) => {
			if (token === GlobalConfig) {
				return mockGlobalConfigInstance;
			}
			if (token === TriggersAndPollers) {
				return mockTriggersAndPollersInstance;
			}
			// Default fallback
			return mockTriggersAndPollersInstance;
		});

		mockAdditionalData = mock<IWorkflowExecuteAdditionalData>({
			executionId: 'test-execution-id',
		});

		mockRunExecutionData = createRunExecutionData();

		mockNode = {
			id: 'test-node-id',
			name: 'Test Node',
			type: 'test-node',
			typeVersion: 1,
			position: [100, 200],
			parameters: {},
		};

		mockNodeType = mock<INodeType>({
			description: {
				displayName: 'Test Node',
				name: 'test-node',
				group: ['transform'],
				version: 1,
				inputs: ['main'],
				outputs: ['main'],
				properties: [],
				requestDefaults: undefined, // Explicitly set to undefined
			},
		});

		mockWorkflow = mock<Workflow>({
			nodeTypes: {
				getByNameAndVersion: vi.fn().mockReturnValue(mockNodeType),
			},
			settings: {
				executionOrder: 'v1',
			},
		});

		mockExecutionData = {
			node: mockNode,
			data: {
				main: [[{ json: { test: 'data' } }]],
			},
			source: null,
		};

		workflowExecute = new WorkflowExecute(mockAdditionalData, 'manual', mockRunExecutionData);
	});

	describe('disabled node handling', () => {
		it('should return undefined for disabled node with no input data', async () => {
			const disabledNode = { ...mockNode, disabled: true };
			const executionData = {
				...mockExecutionData,
				node: disabledNode,
				data: { main: [] },
			};

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual({ data: undefined });
		});

		it('should return undefined for disabled node with null main input', async () => {
			const disabledNode = { ...mockNode, disabled: true };
			const executionData = {
				...mockExecutionData,
				node: disabledNode,
				data: { main: [null] },
			};

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual({ data: undefined });
		});

		it('should pass through first main input data for disabled node', async () => {
			const disabledNode = { ...mockNode, disabled: true };
			const inputData = [{ json: { test: 'passthrough' } }];
			const executionData = {
				...mockExecutionData,
				node: disabledNode,
				data: { main: [inputData] },
			};

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual({ data: [inputData] });
		});
	});

	describe('error handling for previously failed nodes', () => {
		it('should rethrow NodeOperationError from previous execution', async () => {
			const error = new NodeOperationError(mockNode, 'Test error');
			const runDataWithError = {
				...mockRunExecutionData,
				resultData: {
					...mockRunExecutionData.resultData,
					lastNodeExecuted: mockNode.name,
					error,
				},
			};

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					runDataWithError,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow(error);
		});

		it('should rethrow NodeApiError from previous execution', async () => {
			const error = new NodeApiError(mockNode, { message: 'API error' });
			const runDataWithError = {
				...mockRunExecutionData,
				resultData: {
					...mockRunExecutionData.resultData,
					lastNodeExecuted: mockNode.name,
					error,
				},
			};

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					runDataWithError,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow(error);
		});

		it('should throw generic Error for other error types from previous execution', async () => {
			const originalError = new NodeOperationError(mockNode, 'Generic error');
			const runDataWithError = {
				...mockRunExecutionData,
				resultData: {
					...mockRunExecutionData.resultData,
					lastNodeExecuted: mockNode.name,
					error: originalError,
				},
			};

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					runDataWithError,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Generic error');
		});

		it('should create new Error with message and stack for non-n8n error types from previous execution', async () => {
			const originalError = {
				name: 'SomeCustomError',
				message: 'Custom error message',
				stack: 'Custom error stack trace',
			} as unknown as ExecutionBaseError;
			const runDataWithError = {
				...mockRunExecutionData,
				resultData: {
					...mockRunExecutionData.resultData,
					lastNodeExecuted: mockNode.name,
					error: originalError,
				},
			};

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					runDataWithError,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow(Error);

			// Verify the error has the correct message and stack
			try {
				await workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					runDataWithError,
					0,
					mockAdditionalData,
					'manual',
				);
			} catch (error) {
				expect(error).toBeInstanceOf(Error);
				expect((error as Error).message).toBe('Custom error message');
				expect((error as Error).stack).toBe('Custom error stack trace');
			}
		});
	});

	describe('execute node type handling', () => {
		it('should execute custom operation when available', async () => {
			const mockData = [[{ json: { result: 'custom operation result' } }]];
			const mockCustomOperation = vi.fn().mockResolvedValue(mockData);

			// Create a node with parameters that match the custom operation
			const customOpNode = {
				...mockNode,
				parameters: {
					resource: 'testResource',
					operation: 'testOperation',
				},
			};

			// Create a nodeType with customOperations
			const customOpNodeType = {
				...mockNodeType,
				customOperations: {
					testResource: {
						testOperation: mockCustomOperation,
					},
				},
				execute: undefined, // Make sure execute is not defined so custom operation is used
			};

			mockWorkflow.nodeTypes.getByNameAndVersion = vi.fn().mockReturnValue(customOpNodeType);

			const customOpExecutionData = {
				...mockExecutionData,
				node: customOpNode,
			};

			const mockContextInstance = { hints: [] };
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				customOpExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockCustomOperation).toHaveBeenCalledWith();
			expect(result).toEqual({ data: mockData, hints: [] });
		});

		it('should execute node with execute method and return data with hints', async () => {
			const mockData = [[{ json: { result: 'test' } }]];
			const mockHints = [{ message: 'Test hint' }];
			mockNodeType.execute = vi.fn().mockResolvedValue(mockData);

			const mockContextInstance = {
				hints: mockHints,
			};
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockNodeType.execute).toHaveBeenCalled();
			expect(result).toEqual({ data: mockData, hints: mockHints });
		});

		it('should execute Node class instance with execute method', async () => {
			const mockData = [[{ json: { result: 'test' } }]];
			// Create a mock that extends Node to trigger instanceof Node check
			const nodeInstance = Object.create(Node.prototype);
			nodeInstance.execute = vi.fn().mockResolvedValue(mockData);
			nodeInstance.description = {
				displayName: 'Node Instance',
				name: 'node-instance',
				group: ['transform'],
				version: 1,
				inputs: ['main'],
				outputs: ['main'],
				properties: [],
				requestDefaults: undefined,
			};
			(mockWorkflow.nodeTypes.getByNameAndVersion as Mock).mockReturnValue(
				nodeInstance as INodeType,
			);

			const mockContextInstance = { hints: [] };
			const mockSubNodeExecutionResults = undefined;
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(nodeInstance.execute).toHaveBeenCalledWith(
				mockContextInstance,
				mockSubNodeExecutionResults,
			);
			expect(result).toEqual({ data: mockData, hints: [] });
		});

		it('should return undefined when no connection input data for execute nodes', async () => {
			const executionData = {
				...mockExecutionData,
				data: { main: [] }, // No input data
			};

			mockNodeType.execute = vi.fn();

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual({ data: undefined });
			expect(mockNodeType.execute).not.toHaveBeenCalled();
		});

		it('should handle close functions and their errors', async () => {
			const mockData = [[{ json: { result: 'test' } }]];
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockRejectedValue(new Error('Close error'));

			mockNodeType.execute = vi.fn().mockResolvedValue(mockData);

			const mockContextInstance = {
				hints: [],
			};

			// Mock ExecuteContext constructor to capture closeFunctions array
			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						// Add close functions to the array passed in
						closeFunctions.push(closeFunction1, closeFunction2);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Close error');

			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
		});

		it('should throw ApplicationError when close function throws non-Error object', async () => {
			const mockData = [[{ json: { result: 'test' } }]];
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockRejectedValue('String error'); // Non-Error object to trigger line 1247

			mockNodeType.execute = vi.fn().mockResolvedValue(mockData);

			const mockContextInstance = {
				hints: [],
			};

			// Mock ExecuteContext constructor to capture closeFunctions array
			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						// Add close functions to the array passed in
						closeFunctions.push(closeFunction1, closeFunction2);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow("Error on execution node's close function(s)");

			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
		});

		it('should call close functions when execute returns an EngineRequest', async () => {
			const engineRequest = { actions: [{ type: 'test' }], metadata: {} };
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockResolvedValue(undefined);

			mockNodeType.execute = vi.fn().mockResolvedValue(engineRequest);

			const mockContextInstance = {
				hints: [],
			};

			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1, closeFunction2);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual(engineRequest);
			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
		});

		it('should call close functions when execute throws an error', async () => {
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockResolvedValue(undefined);

			mockNodeType.execute = vi.fn().mockRejectedValue(new Error('Execution failed'));

			const mockContextInstance = {
				hints: [],
			};

			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1, closeFunction2);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Execution failed');

			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
		});

		it('should call all close functions via Promise.allSettled even when some fail', async () => {
			const mockData = [[{ json: { result: 'test' } }]];
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockRejectedValue(new Error('Close error 1'));
			const closeFunction3 = vi.fn().mockResolvedValue(undefined);

			mockNodeType.execute = vi.fn().mockResolvedValue(mockData);

			const mockContextInstance = {
				hints: [],
			};

			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1, closeFunction2, closeFunction3);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Close error 1');

			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
			expect(closeFunction3).toHaveBeenCalled();
		});

		it('should throw close function error when EngineRequest is returned', async () => {
			const engineRequest = { actions: [{ type: 'test' }], metadata: {} };
			const closeFunction1 = vi.fn().mockRejectedValue(new Error('Close error on EngineRequest'));

			mockNodeType.execute = vi.fn().mockResolvedValue(engineRequest);

			const mockContextInstance = {
				hints: [],
			};

			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Close error on EngineRequest');

			expect(closeFunction1).toHaveBeenCalled();
		});

		it('should call close functions after custom operation completes', async () => {
			const mockData = [[{ json: { result: 'custom operation result' } }]];
			const mockCustomOperation = vi.fn().mockResolvedValue(mockData);
			const closeFunction1 = vi.fn().mockResolvedValue(undefined);
			const closeFunction2 = vi.fn().mockResolvedValue(undefined);

			const customOpNode = {
				...mockNode,
				parameters: {
					resource: 'testResource',
					operation: 'testOperation',
				},
			};

			const customOpNodeType = {
				...mockNodeType,
				customOperations: {
					testResource: {
						testOperation: mockCustomOperation,
					},
				},
				execute: undefined,
			};

			mockWorkflow.nodeTypes.getByNameAndVersion = vi.fn().mockReturnValue(customOpNodeType);

			const customOpExecutionData = {
				...mockExecutionData,
				node: customOpNode,
			};

			const mockContextInstance = { hints: [] };
			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1, closeFunction2);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			const result = await workflowExecute.runNode(
				mockWorkflow,
				customOpExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockCustomOperation).toHaveBeenCalled();
			expect(result).toEqual({ data: mockData, hints: [] });
			expect(closeFunction1).toHaveBeenCalled();
			expect(closeFunction2).toHaveBeenCalled();
		});

		it('should not mask execution error with close function error', async () => {
			const closeFunction1 = vi.fn().mockRejectedValue(new Error('Close error'));

			mockNodeType.execute = vi.fn().mockRejectedValue(new Error('Execution failed'));

			const mockContextInstance = {
				hints: [],
			};

			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						_inputData,
						_executionData,
						closeFunctions,
					) => {
						closeFunctions.push(closeFunction1);
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('Execution failed');

			expect(closeFunction1).toHaveBeenCalled();
		});
	});

	describe('poll node type handling', () => {
		it('should execute poll function in manual mode', async () => {
			const mockData = [[{ json: { polled: 'data' } }]];
			mockNodeType.poll = vi.fn().mockResolvedValue(mockData);
			mockNodeType.execute = undefined;

			const mockContextInstance = {};
			mockPollContext.mockImplementation(function () {
				return mockContextInstance as unknown as PollContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockPollContext).toHaveBeenCalledWith(
				mockWorkflow,
				mockNode,
				mockAdditionalData,
				'manual',
				'manual',
			);
			expect(mockNodeType.poll).toHaveBeenCalledWith();
			expect(result).toEqual({ data: mockData });
		});

		it('should pass through input data for poll nodes in non-manual mode', async () => {
			mockNodeType.poll = vi.fn();
			mockNodeType.execute = undefined;

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'trigger', // Non-manual mode
			);

			expect(mockNodeType.poll).not.toHaveBeenCalled();
			expect(result).toEqual({ data: mockExecutionData.data.main });
		});
	});

	describe('trigger node type handling', () => {
		it('should run trigger in manual mode and return data', async () => {
			const mockTriggerData = [[{ json: { triggered: 'data' } }]];
			const mockTriggerResponse = {
				manualTriggerResponse: Promise.resolve(mockTriggerData),
			};

			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const mockTriggersAndPollersInstance = {
				runTrigger: vi.fn().mockResolvedValue(mockTriggerResponse),
			};
			const mockGlobalConfigInstance = {
				sentry: { backendDsn: '' },
			};
			mockContainer.get.mockImplementation((token) => {
				if (token === GlobalConfig) {
					return mockGlobalConfigInstance;
				}
				if (token === TriggersAndPollers) {
					return mockTriggersAndPollersInstance;
				}
				return mockTriggersAndPollersInstance;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockTriggersAndPollersInstance.runTrigger).toHaveBeenCalled();
			expect(result).toEqual({ data: mockTriggerData });
		});

		it('should return null data when trigger response is undefined in manual mode', async () => {
			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const mockTriggersAndPollersInstance = {
				runTrigger: vi.fn().mockResolvedValue(undefined), // Return undefined to trigger line 1277
			};
			const mockGlobalConfigInstance = {
				sentry: { backendDsn: '' },
			};
			mockContainer.get.mockImplementation((token) => {
				if (token === GlobalConfig) {
					return mockGlobalConfigInstance;
				}
				if (token === TriggersAndPollers) {
					return mockTriggersAndPollersInstance;
				}
				return mockTriggersAndPollersInstance;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockTriggersAndPollersInstance.runTrigger).toHaveBeenCalled();
			expect(result).toEqual({ data: null });
		});

		it('should return null data and closeFunction when trigger response is empty in manual mode', async () => {
			const mockCloseFunction = vi.fn();
			const mockTriggerResponse = {
				manualTriggerResponse: Promise.resolve([]), // Empty response to trigger line 1301
				closeFunction: mockCloseFunction,
			};

			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const mockTriggersAndPollersInstance = {
				runTrigger: vi.fn().mockResolvedValue(mockTriggerResponse),
			};
			const mockGlobalConfigInstance = {
				sentry: { backendDsn: '' },
			};
			mockContainer.get.mockImplementation((token) => {
				if (token === GlobalConfig) {
					return mockGlobalConfigInstance;
				}
				if (token === TriggersAndPollers) {
					return mockTriggersAndPollersInstance;
				}
				return mockTriggersAndPollersInstance;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockTriggersAndPollersInstance.runTrigger).toHaveBeenCalled();
			expect(result).toEqual({ data: null, closeFunction: mockCloseFunction });
		});

		it('should call manualTriggerFunction when defined in trigger response', async () => {
			const mockTriggerData = [[{ json: { triggered: 'data' } }]];
			const mockManualTriggerFunction = vi.fn().mockResolvedValue(undefined);
			const mockCloseFunction = vi.fn();
			const mockTriggerResponse = {
				manualTriggerResponse: Promise.resolve(mockTriggerData),
				manualTriggerFunction: mockManualTriggerFunction, // This will trigger line 1294
				closeFunction: mockCloseFunction,
			};

			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const mockTriggersAndPollersInstance = {
				runTrigger: vi.fn().mockResolvedValue(mockTriggerResponse),
			};
			const mockGlobalConfigInstance = {
				sentry: { backendDsn: '' },
			};
			mockContainer.get.mockImplementation((token) => {
				if (token === GlobalConfig) {
					return mockGlobalConfigInstance;
				}
				if (token === TriggersAndPollers) {
					return mockTriggersAndPollersInstance;
				}
				return mockTriggersAndPollersInstance;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockTriggersAndPollersInstance.runTrigger).toHaveBeenCalled();
			expect(mockManualTriggerFunction).toHaveBeenCalled(); // Verify line 1294 was executed
			expect(result).toEqual({ data: mockTriggerData, closeFunction: mockCloseFunction });
		});

		it('should pass through input data for trigger nodes in non-manual mode', async () => {
			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'trigger', // Non-manual mode
			);

			expect(result).toEqual({ data: mockExecutionData.data.main });
		});
	});

	describe('webhook node type handling', () => {
		it('should pass through input data for non-declarative webhook nodes', async () => {
			mockNodeType.webhook = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.supplyData = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.trigger = undefined;
			mockNodeType.description.requestDefaults = undefined; // Non-declarative

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(result).toEqual({ data: mockExecutionData.data.main });
		});

		it('should execute declarative webhook nodes through routing node', async () => {
			const mockData = [[{ json: { webhook: 'result' } }]];
			mockNodeType.webhook = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.supplyData = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.trigger = undefined;
			mockNodeType.description.requestDefaults = {}; // Declarative node

			const mockRoutingNodeInstance = {
				runNode: vi.fn().mockResolvedValue(mockData),
			};
			mockRoutingNode.mockImplementation(function () {
				return mockRoutingNodeInstance as unknown as RoutingNode;
			});

			const mockContextInstance = {};
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockRoutingNode).toHaveBeenCalledWith(mockContextInstance, mockNodeType);
			expect(mockRoutingNodeInstance.runNode).toHaveBeenCalled();
			expect(result).toEqual({ data: mockData });
		});
	});

	describe('fallback routing node handling', () => {
		it('should use routing node for nodes without specific execution methods', async () => {
			const mockData = [[{ json: { routed: 'result' } }]];
			// Node with no execute, poll, trigger, or webhook methods
			mockNodeType.execute = undefined;
			mockNodeType.supplyData = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.trigger = undefined;
			mockNodeType.webhook = undefined;

			const mockRoutingNodeInstance = {
				runNode: vi.fn().mockResolvedValue(mockData),
			};
			mockRoutingNode.mockImplementation(function () {
				return mockRoutingNodeInstance as unknown as RoutingNode;
			});

			const mockContextInstance = {};
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				mockExecutionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockRoutingNode).toHaveBeenCalledWith(mockContextInstance, mockNodeType);
			expect(mockRoutingNodeInstance.runNode).toHaveBeenCalled();
			expect(result).toEqual({ data: mockData });
		});
	});

	describe('supplyData node handling', () => {
		it('should throw a clear error when a supplyData node has no execute method', async () => {
			mockNodeType.execute = undefined;
			mockNodeType.supplyData = vi.fn();
			mockNodeType.poll = undefined;
			mockNodeType.trigger = undefined;
			mockNodeType.webhook = undefined;

			await expect(
				workflowExecute.runNode(
					mockWorkflow,
					mockExecutionData,
					mockRunExecutionData,
					0,
					mockAdditionalData,
					'manual',
				),
			).rejects.toThrow('has a "supplyData" method but no "execute" method');
		});
	});

	describe('executeOnce node handling', () => {
		it('should slice input data to only first item when executeOnce is true', async () => {
			const executeOnceNode = { ...mockNode, executeOnce: true };
			// Create input data with multiple connection types to trigger the slice logic in line 1183
			const inputData = {
				main: [
					[{ json: { item: 1 } }, { json: { item: 2 } }, { json: { item: 3 } }], // This should be sliced to only first item
				],
				ai_tool: [
					[{ json: { tool: 'a' } }, { json: { tool: 'b' } }], // This should also be sliced to only first item
				],
			};
			const executionData = {
				...mockExecutionData,
				node: executeOnceNode,
				data: inputData,
			};

			let capturedInputData: ITaskDataConnections | undefined;

			mockNodeType.execute = vi.fn().mockResolvedValue([[{ json: { result: 'executeOnce' } }]]);

			const mockContextInstance = { hints: [] };
			mockExecuteContext.mockImplementation(
				ctor(
					(
						_workflow,
						_node,
						_additionalData,
						_mode,
						_runExecutionData,
						_runIndex,
						_connectionInputData,
						inputData,
						_executionData,
						_closeFunctions,
					) => {
						// Capture the inputData that was passed to ExecuteContext
						capturedInputData = inputData;
						return mockContextInstance as unknown as ExecuteContext;
					},
				),
			);

			await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			// Verify that the slice logic was applied correctly to ALL connection types
			expect(capturedInputData).toBeDefined();
			if (capturedInputData) {
				// Main connection should be sliced to only first item
				expect(capturedInputData.main).toHaveLength(1);
				expect(capturedInputData.main[0]).toHaveLength(1);
				expect(capturedInputData.main[0]![0]).toEqual({ json: { item: 1 } });

				// AI tool connection should also be sliced to only first item
				expect(capturedInputData.ai_tool).toHaveLength(1);
				expect(capturedInputData.ai_tool[0]).toHaveLength(1);
				expect(capturedInputData.ai_tool[0]![0]).toEqual({ json: { tool: 'a' } });
			}
			expect(mockNodeType.execute).toHaveBeenCalled();
		});
	});

	describe('execution order and input data handling', () => {
		it('should use first main input for v1 execution order when forceInputNodeExecution is false', async () => {
			mockWorkflow.settings.executionOrder = 'v1'; // v1 means forceInputNodeExecution = false
			const inputData = [
				[], // Empty first input
				[{ json: { item: 2 } }], // Non-empty second input
				[{ json: { item: 3 } }],
			];
			const executionData = {
				...mockExecutionData,
				data: { main: inputData },
			};

			mockNodeType.execute = vi.fn().mockResolvedValue([[{ json: { result: 'test' } }]]);

			const mockContextInstance = { hints: [] };
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			// Should find the first non-empty input and execute
			expect(result).toEqual({ data: [[{ json: { result: 'test' } }]], hints: [] });
		});

		it('should use first main input for v0 execution order when forceInputNodeExecution is true', async () => {
			mockWorkflow.settings.executionOrder = 'v0'; // v0 means forceInputNodeExecution = true
			const inputData = [
				[], // Empty first input
				[{ json: { item: 2 } }], // Non-empty second input
				[{ json: { item: 3 } }],
			];
			const executionData = {
				...mockExecutionData,
				data: { main: inputData },
			};

			mockNodeType.execute = vi.fn().mockResolvedValue([[{ json: { result: 'test' } }]]);

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			// Should return undefined because first input is empty and we use first input in v0
			expect(result).toEqual({ data: undefined });
		});
	});

	describe('customTelemetryTags', () => {
		let getParameterValue: Mock;

		beforeEach(() => {
			getParameterValue = vi.fn();
			mockWorkflow.expression = {
				getParameterValue,
			} as unknown as Workflow['expression'];

			mockAdditionalData.webhookWaitingBaseUrl = 'https://n8n.local/webhook-waiting';
			mockAdditionalData.formWaitingBaseUrl = 'https://n8n.local/form-waiting';
			mockAdditionalData.variables = {};

			mockNodeType.execute = vi.fn().mockResolvedValue([[{ json: {} }]]);
			const mockContextInstance = { hints: [] };
			mockExecuteContext.mockImplementation(function () {
				return mockContextInstance as unknown as ExecuteContext;
			});
		});

		const makeTelemetryExecutionData = (overrides: Partial<IExecuteData> = {}): IExecuteData => ({
			...mockExecutionData,
			data: { main: [[{ json: { env: 'prod' } }]] },
			source: null,
			...overrides,
		});

		const runNodeForTelemetry = async (executionData: IExecuteData) => {
			await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);
		};

		it('evaluates tag expressions and writes them into metadata.tracing', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: 'env', value: '={{ $json.env }}' },
						{ key: 'static', value: 'foo' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) =>
				value === '={{ $json.env }}' ? 'prod' : value,
			);

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ env: 'prod', static: 'foo' });
		});

		it('preserves existing tracing entries on key collision', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [{ key: 'env', value: 'user-set' }],
				},
			};
			getParameterValue.mockReturnValue('user-set');

			const executionData = makeTelemetryExecutionData({
				node,
				metadata: { tracing: { env: 'node-authored' } },
			});

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ env: 'node-authored' });
		});

		it('skips tags with empty or whitespace keys', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: '   ', value: 'ignored' },
						{ key: 'kept', value: 'value' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) => value);

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ kept: 'value' });
		});

		it('preserves string, number, and boolean evaluated values', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: 'count', value: '={{ 42 }}' },
						{ key: 'enabled', value: '={{ true }}' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) => (value === '={{ 42 }}' ? 42 : true));

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ count: 42, enabled: true });
		});

		it('skips tags when expression evaluates to a non-primitive value', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: 'obj', value: '={{ $json }}' },
						{ key: 'ok', value: 'still-here' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) =>
				value === '={{ $json }}' ? { nested: 1 } : value,
			);

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ ok: 'still-here' });
		});

		it('ignores tags whose expression evaluates to null or undefined', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: 'maybe', value: '={{ $json.missing }}' },
						{ key: 'definitely', value: 'value' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) =>
				value === '={{ $json.missing }}' ? undefined : value,
			);

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ definitely: 'value' });
		});

		it('does not modify metadata when customTelemetryTags is absent', async () => {
			const executionData = makeTelemetryExecutionData();

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata).toBeUndefined();
		});

		it('continues evaluating remaining tags after one expression throws', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [
						{ key: 'broken', value: '={{ $json.missing.deep }}' },
						{ key: 'ok', value: 'value' },
					],
				},
			};
			getParameterValue.mockImplementation((value: string) => {
				if (value === '={{ $json.missing.deep }}') throw new Error('boom');
				return value;
			});

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ ok: 'value' });
		});

		it('writes tracing for trigger nodes', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [{ key: 'env', value: 'prod' }],
				},
			};
			getParameterValue.mockReturnValue('prod');

			mockNodeType.trigger = vi.fn();
			mockNodeType.execute = undefined;
			mockNodeType.poll = undefined;
			mockNodeType.webhook = undefined;

			const mockTriggersAndPollersInstance = {
				runTrigger: vi.fn().mockResolvedValue({
					manualTriggerResponse: Promise.resolve([[{ json: { triggered: 'data' } }]]),
				}),
			};
			mockContainer.get.mockImplementation((token) => {
				if (token === TriggersAndPollers) return mockTriggersAndPollersInstance;
				return { sentry: { backendDsn: '' } };
			});

			const executionData = makeTelemetryExecutionData({ node });

			await runNodeForTelemetry(executionData);

			expect(executionData.metadata?.tracing).toEqual({ env: 'prod' });
		});

		it('writes tracing for poll nodes in non-manual mode', async () => {
			const node: INode = {
				...mockNode,
				customTelemetryTags: {
					tag: [{ key: 'env', value: 'staging' }],
				},
			};
			getParameterValue.mockReturnValue('staging');

			mockNodeType.poll = vi.fn();
			mockNodeType.execute = undefined;

			const executionData = makeTelemetryExecutionData({ node });

			await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'trigger',
			);

			expect(executionData.metadata?.tracing).toEqual({ env: 'staging' });
		});

		it('writes tracing when execution uses a custom operation instead of execute()', async () => {
			const mockData = [[{ json: { result: 'custom operation result' } }]];
			const mockCustomOperation = vi.fn().mockResolvedValue(mockData);

			const customOpNode: INode = {
				...mockNode,
				parameters: {
					resource: 'testResource',
					operation: 'testOperation',
				},
				customTelemetryTags: {
					tag: [{ key: 'env', value: '={{ $json.env }}' }],
				},
			};

			const customOpNodeType = {
				...mockNodeType,
				customOperations: {
					testResource: {
						testOperation: mockCustomOperation,
					},
				},
				execute: undefined,
			};

			mockWorkflow.nodeTypes.getByNameAndVersion = vi.fn().mockReturnValue(customOpNodeType);

			getParameterValue.mockImplementation((value: string) =>
				value === '={{ $json.env }}' ? 'prod' : value,
			);

			const executionData = makeTelemetryExecutionData({ node: customOpNode });

			const result = await workflowExecute.runNode(
				mockWorkflow,
				executionData,
				mockRunExecutionData,
				0,
				mockAdditionalData,
				'manual',
			);

			expect(mockCustomOperation).toHaveBeenCalled();
			expect(result).toEqual({ data: mockData, hints: [] });
			expect(executionData.metadata?.tracing).toEqual({ env: 'prod' });
		});
	});
});
