import type {
	IExecuteSingleFunctions,
	IN8nHttpFullResponse,
	INodeExecutionData,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';

import {
	agentErrorPostReceive,
	embeddingsErrorPostReceive,
	searchErrorPostReceive,
	sendErrorPostReceive,
} from '../GenericFunctions';

// Mock implementation for `this` in `sendErrorPostReceive`
const mockExecuteSingleFunctions = {
	getNode: () => ({
		name: 'Mock Node',
		type: 'mock-type',
		position: [0, 0],
	}),
} as unknown as IExecuteSingleFunctions;

describe('Generic Functions', () => {
	describe('sendErrorPostReceive', () => {
		let testData: INodeExecutionData[];
		let testResponse: IN8nHttpFullResponse;

		beforeEach(() => {
			testData = [{ json: {} }];
			testResponse = { statusCode: 200, headers: {}, body: {} };
		});

		it('should return data if status code is not 4xx or 5xx', async () => {
			const result = await sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				testResponse,
			);
			expect(result).toEqual(testData);
		});

		it('should throw NodeApiError if status code is 4xx', async () => {
			testResponse.statusCode = 400;
			await expect(
				sendErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should throw NodeApiError if status code is 5xx', async () => {
			testResponse.statusCode = 500;
			await expect(
				sendErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should throw NodeApiError with "Invalid model" message if error type is invalid_model', async () => {
			const errorResponse = {
				statusCode: 400,
				body: {
					error: {
						type: 'invalid_model',
						message: 'Invalid model type provided',
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('Invalid model');
		});

		it('should throw NodeApiError with "Invalid parameter" message if error type is invalid_parameter', async () => {
			const errorResponse = {
				statusCode: 400,
				body: {
					error: {
						type: 'invalid_parameter',
						message: 'Invalid parameter provided',
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('Invalid parameter provided.');
		});

		it('should handle "invalid_model" error with itemIndex', async () => {
			const errorResponse = {
				statusCode: 400,
				body: {
					error: {
						type: 'invalid_model',
						message: 'Invalid model',
						itemIndex: 0,
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('Invalid model');
		});

		it('should handle "invalid_parameter" error with non-string message', async () => {
			const errorResponse = {
				statusCode: 400,
				body: {
					error: {
						type: 'invalid_parameter',
						message: { detail: 'Invalid param' },
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('An unexpected issue occurred.');
		});

		it('should throw generic error for unknown error type', async () => {
			const errorResponse = {
				statusCode: 500,
				body: {
					error: {
						type: 'server_error',
						message: 'Internal server error',
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('Internal server error.');
		});

		it('should include itemIndex in error message when present', async () => {
			const errorResponse = {
				statusCode: 400,
				body: {
					error: {
						type: 'other_error',
						message: 'Error with item',
						itemIndex: 2,
					},
				},
			};

			const execution = sendErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				errorResponse as unknown as IN8nHttpFullResponse,
			);
			await expect(execution).rejects.toThrow(NodeApiError);
			await expect(execution).rejects.toThrow('Error with item [Item 2].');
		});
	});

	describe('agentErrorPostReceive', () => {
		let testData: INodeExecutionData[];
		let testResponse: IN8nHttpFullResponse;

		beforeEach(() => {
			testData = [{ json: {} }];
			testResponse = { statusCode: 200, headers: {}, body: {} };
		});

		it('should return data if status code is not 4xx or 5xx', async () => {
			const result = await agentErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				testResponse,
			);
			expect(result).toEqual(testData);
		});

		it('should throw NodeApiError if status code is 4xx', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Bad request' } };
			await expect(
				agentErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should throw NodeApiError if status code is 5xx', async () => {
			testResponse.statusCode = 500;
			testResponse.body = { error: { message: 'Server error' } };
			await expect(
				agentErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should use statusMessage as fallback when error message is not a string', async () => {
			const errorResponse = {
				statusCode: 400,
				statusMessage: 'Bad Request',
				headers: {},
				body: { error: { message: { detail: 'complex error' } } },
			};
			await expect(
				agentErrorPostReceive.call(
					mockExecuteSingleFunctions,
					testData,
					errorResponse as unknown as IN8nHttpFullResponse,
				),
			).rejects.toThrow(NodeApiError);
		});

		it('should include agent API documentation in the description', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Invalid param' } };
			await expect(
				agentErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrowError(
				expect.objectContaining({
					description: expect.stringContaining(
						'https://docs.perplexity.ai/api-reference/agent-post',
					),
				}),
			);
		});
	});

	describe('searchErrorPostReceive', () => {
		let testData: INodeExecutionData[];
		let testResponse: IN8nHttpFullResponse;

		beforeEach(() => {
			testData = [{ json: {} }];
			testResponse = { statusCode: 200, headers: {}, body: {} };
		});

		it('should return data if status code is not 4xx or 5xx', async () => {
			const result = await searchErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				testResponse,
			);
			expect(result).toEqual(testData);
		});

		it('should throw NodeApiError if status code is 4xx', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Bad request' } };
			await expect(
				searchErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should throw NodeApiError if status code is 5xx', async () => {
			testResponse.statusCode = 500;
			testResponse.body = { error: { message: 'Server error' } };
			await expect(
				searchErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should include search API documentation in the description', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Invalid query' } };
			await expect(
				searchErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrowError(
				expect.objectContaining({
					description: expect.stringContaining(
						'https://docs.perplexity.ai/api-reference/search-post',
					),
				}),
			);
		});
	});

	describe('embeddingsErrorPostReceive', () => {
		let testData: INodeExecutionData[];
		let testResponse: IN8nHttpFullResponse;

		beforeEach(() => {
			testData = [{ json: {} }];
			testResponse = { statusCode: 200, headers: {}, body: {} };
		});

		it('should return data if status code is not 4xx or 5xx', async () => {
			const result = await embeddingsErrorPostReceive.call(
				mockExecuteSingleFunctions,
				testData,
				testResponse,
			);
			expect(result).toEqual(testData);
		});

		it('should throw NodeApiError if status code is 4xx', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Bad request' } };
			await expect(
				embeddingsErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should throw NodeApiError if status code is 5xx', async () => {
			testResponse.statusCode = 500;
			testResponse.body = { error: { message: 'Server error' } };
			await expect(
				embeddingsErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrow(NodeApiError);
		});

		it('should include embeddings API documentation in the description', async () => {
			testResponse.statusCode = 400;
			testResponse.body = { error: { message: 'Invalid input' } };
			await expect(
				embeddingsErrorPostReceive.call(mockExecuteSingleFunctions, testData, testResponse),
			).rejects.toThrowError(
				expect.objectContaining({
					description: expect.stringContaining(
						'https://docs.perplexity.ai/api-reference/embeddings-post',
					),
				}),
			);
		});
	});
});
