import { NodeOperationError, type IExecuteFunctions, type INode } from 'n8n-workflow';

import * as transport from '../../v2//transport/discord.api';
import {
	createSimplifyFunction,
	prepareOptions,
	prepareEmbeds,
	checkAccessToGuild,
	setupChannelGetter,
	parseDiscordError,
} from '../../v2/helpers/utils';

const node: INode = {
	id: '1',
	name: 'Discord node',
	typeVersion: 2,
	type: 'n8n-nodes-base.discord',
	position: [60, 760],
	parameters: {
		resource: 'channel',
		operation: 'get',
	},
};

describe('Test Discord > createSimplifyFunction', () => {
	it('should create function', () => {
		const result = createSimplifyFunction(['message_reference']);
		expect(result).toBeDefined();
		expect(typeof result).toBe('function');
	});

	it('should return object containing only specified fields', () => {
		const simplify = createSimplifyFunction(['id', 'name']);
		const data = {
			id: '123',
			name: 'test',
			type: 'test',
			randomField: 'test',
		};
		const result = simplify(data);
		expect(result).toEqual({
			id: '123',
			name: 'test',
		});
	});
});

describe('Test Discord > prepareOptions', () => {
	it('should return correct flag value', () => {
		const result = prepareOptions({
			flags: ['SUPPRESS_EMBEDS', 'SUPPRESS_NOTIFICATIONS'],
		});
		expect(result.flags).toBe((1 << 2) + (1 << 12));
	});

	it('should convert message_reference', () => {
		const result = prepareOptions(
			{
				message_reference: '123456',
			},
			'789000',
		);
		expect(result.message_reference).toEqual({
			message_id: '123456',
			guild_id: '789000',
		});
	});
});

describe('Test Discord > prepareEmbeds', () => {
	const executeFunction = {
		getNode: () => node,
	} as unknown as IExecuteFunctions;

	it('should return return empty object removing empty strings', () => {
		const embeds = [
			{
				test1: 'test',
				test2: 'test',
				description: 'test',
			},
		];

		const result = prepareEmbeds.call(executeFunction, embeds);

		expect(result).toEqual(embeds);
	});

	it('should normalize string author from JSON embed to { name }', () => {
		const embeds = [
			{
				inputMethod: 'json',
				json: JSON.stringify({
					title: 'T',
					author: 'Plain',
				}),
			},
		];

		const result = prepareEmbeds.call(executeFunction, embeds);

		expect(result).toEqual([
			{
				title: 'T',
				author: { name: 'Plain' },
			},
		]);
	});

	it('should preserve object author from JSON embed (icon_url, url)', () => {
		const author = {
			name: 'N',
			icon_url: 'https://example.com/x.png',
			url: 'https://example.com',
		};
		const embeds = [
			{
				inputMethod: 'json',
				json: JSON.stringify({
					title: 'T',
					author,
				}),
			},
		];

		const result = prepareEmbeds.call(executeFunction, embeds);

		expect(result).toEqual([
			{
				title: 'T',
				author,
			},
		]);
	});
});

describe('Test Discord > checkAccessToGuild', () => {
	it('should throw error', () => {
		const guildId = '123456';
		const guilds = [
			{
				id: '789000',
			},
		];

		expect(() => {
			checkAccessToGuild(node, guildId, guilds);
		}).toThrow('You do not have access to the guild with the id 123456');
	});

	it('should pass', () => {
		const guildId = '123456';
		const guilds = [
			{
				id: '123456',
			},
			{
				id: '789000',
			},
		];

		expect(() => {
			checkAccessToGuild(node, guildId, guilds);
		}).not.toThrow();
	});
});

describe('Test Discord > setupChannelGetter & checkAccessToChannel', () => {
	beforeEach(() => {
		vi.spyOn(transport, 'discordApiRequest').mockImplementation(async (method: string) => {
			if (method === 'GET') {
				return {
					guild_id: '123456',
				};
			}
		});
	});

	it('should setup channel getter and get channel id', async () => {
		const fakeExecuteFunction = (auth: string) => {
			return {
				getNodeParameter: (parameter: string) => {
					if (parameter === 'authentication') return auth;
					if (parameter === 'channelId') return '42';
				},
				getNode: () => node,
			} as unknown as IExecuteFunctions;
		};

		const userGuilds = [
			{
				id: '789000',
			},
		];

		try {
			const getChannel = await setupChannelGetter.call(fakeExecuteFunction('oAuth2'), userGuilds);
			await getChannel(0);
		} catch (error) {
			expect(error.message).toBe('You do not have access to the guild with the id 123456');
		}

		const getChannel = await setupChannelGetter.call(fakeExecuteFunction('botToken'), userGuilds);
		const channelId = await getChannel(0);
		expect(channelId).toBe('42');
	});
});

describe('Test Discord > parseDiscordError', () => {
	const errorMessage =
		'400 - {"message":"Invalid Form Body","code":50035,"errors":{"embeds":{"0":{"description":{"_errors":[{"code":"BASE_TYPE_REQUIRED","message":"This field is required"}]}}}}}';

	it('should parse error with description', () => {
		const error = {
			cause: {
				error: 'Invalid Form Body',
			},
			description: errorMessage,
		};
		const executeFunction = {
			getNode: () => {},
		} as unknown as IExecuteFunctions;

		const result = parseDiscordError.call(executeFunction, error);
		expect(result).toBeInstanceOf(NodeOperationError);
		expect(result.message).toBe('Invalid Form Body');
	});

	it('should parse error with messages array', () => {
		const error = {
			cause: undefined,
			messages: [errorMessage],
		};
		const executeFunction = {
			getNode: () => {},
		} as unknown as IExecuteFunctions;

		const result = parseDiscordError.call(executeFunction, error);
		expect(result).toBeInstanceOf(NodeOperationError);
		expect(result.message).toBe('Invalid Form Body');
	});
});
