import type express from 'express';
import { mock } from 'jest-mock-extended';
import { FORM_NODE_TYPE, WAITING_FORMS_EXECUTION_STATUS, type Workflow } from 'n8n-workflow';

import type { ExecutionRepository } from '@/databases/repositories/execution.repository';
import { WaitingForms } from '@/webhooks/waiting-forms';

import type { IExecutionResponse } from '../../interfaces';
import type { WaitingWebhookRequest } from '../webhook.types';

describe('WaitingForms', () => {
	const executionRepository = mock<ExecutionRepository>();
	const waitingForms = new WaitingForms(mock(), mock(), executionRepository, mock());

	beforeEach(() => {
		jest.restoreAllMocks();
	});

	describe('findCompletionPage', () => {
		it('should return lastNodeExecuted if it is a non-disabled form completion node', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue([]),
				nodes: {
					Form1: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
				},
			});

			const result = waitingForms.findCompletionPage(workflow, {}, 'Form1');
			expect(result).toBe('Form1');
		});

		it('should return undefined if lastNodeExecuted is disabled', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue([]),
				nodes: {
					Form1: {
						disabled: true,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
				},
			});

			const result = waitingForms.findCompletionPage(workflow, {}, 'Form1');
			expect(result).toBeUndefined();
		});

		it('should return undefined if lastNodeExecuted is not a form node', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue([]),
				nodes: {
					NonForm: {
						disabled: undefined,
						type: 'other-node-type',
						parameters: {},
					},
				},
			});

			const result = waitingForms.findCompletionPage(workflow, {}, 'NonForm');
			expect(result).toBeUndefined();
		});

		it('should return undefined if lastNodeExecuted operation is not completion', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue([]),
				nodes: {
					Form1: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'page',
						},
					},
				},
			});

			const result = waitingForms.findCompletionPage(workflow, {}, 'Form1');
			expect(result).toBeUndefined();
		});

		it('should find first valid completion form in parent nodes if lastNodeExecuted is not valid', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2', 'Form3']),
				nodes: {
					LastNode: {
						disabled: undefined,
						type: 'other-node-type',
						parameters: {},
					},
					Form1: {
						disabled: true,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
					Form2: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
					Form3: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
				},
			});

			const runData = {
				Form2: [],
				Form3: [],
			};

			const result = waitingForms.findCompletionPage(workflow, runData, 'LastNode');
			expect(result).toBe('Form3');
		});

		it('should return undefined if no valid completion form is found in parent nodes', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2']),
				nodes: {
					LastNode: {
						disabled: undefined,
						type: 'other-node-type',
						parameters: {},
					},
					Form1: {
						disabled: true,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
					Form2: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'submit',
						},
					},
				},
			});

			const result = waitingForms.findCompletionPage(workflow, {}, 'LastNode');
			expect(result).toBeUndefined();
		});

		it('should skip parent nodes without runData', () => {
			const workflow = mock<Workflow>({
				getParentNodes: jest.fn().mockReturnValue(['Form1', 'Form2', 'Form3']),
				nodes: {
					LastNode: {
						disabled: undefined,
						type: 'other-node-type',
						parameters: {},
					},
					Form1: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
					Form2: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
					Form3: {
						disabled: undefined,
						type: FORM_NODE_TYPE,
						parameters: {
							operation: 'completion',
						},
					},
				},
			});

			const runData = {
				Form2: [],
			};

			const result = waitingForms.findCompletionPage(workflow, runData, 'LastNode');
			expect(result).toBe('Form2');
		});

		it('should mark as test form webhook when execution mode is manual', async () => {
			jest
				// @ts-expect-error Protected method
				.spyOn(waitingForms, 'getWebhookExecutionData')
				// @ts-expect-error Protected method
				.mockResolvedValue(mock<IWebhookResponseCallbackData>());

			const execution = mock<IExecutionResponse>({
				finished: false,
				mode: 'manual',
				data: {
					resultData: { lastNodeExecuted: 'someNode', error: undefined },
				},
			});
			executionRepository.findSingleExecution.mockResolvedValue(execution);

			await waitingForms.executeWebhook(mock<WaitingWebhookRequest>(), mock<express.Response>());

			expect(execution.data.isTestWebhook).toBe(true);
		});

		it('should return status of execution if suffix is WAITING_FORMS_EXECUTION_STATUS', async () => {
			const execution = mock<IExecutionResponse>({
				status: 'success',
			});
			executionRepository.findSingleExecution.mockResolvedValue(execution);

			const res = mock<express.Response>();

			const result = await waitingForms.executeWebhook(
				{
					params: {
						path: '123',
						suffix: WAITING_FORMS_EXECUTION_STATUS,
					},
				} as WaitingWebhookRequest,
				res,
			);

			expect(result).toEqual({ noWebhookResponse: true });
			expect(res.send).toHaveBeenCalledWith(execution.status);
		});
	});
});
