import type {
	IExecuteSingleFunctions,
	IHookFunctions,
	IHttpRequestOptions,
	IRequestOptions,
	IWebhookFunctions,
	JsonObject,
} from 'n8n-workflow';
import { BINARY_ENCODING, jsonParse, NodeOperationError } from 'n8n-workflow';
import MailComposer from 'nodemailer/lib/mail-composer';
export namespace BrevoNode {
	type ValidEmailFields = { to: string } | { sender: string } | { cc: string } | { bcc: string };
	type Address = { address: string; name?: string };
	type Email = { email: string; name?: string };
	type ToEmail = { to: Email[] };
	type SenderEmail = { sender: Email };
	type CCEmail = { cc: Email[] };
	type BBCEmail = { bbc: Email[] };
	type ValidatedEmail = ToEmail | SenderEmail | CCEmail | BBCEmail;

	const OVERRIDE_MAP_VALUES = {
		CATEGORY: 'category',
		NORMAL: 'boolean',
		TRANSACTIONAL: 'id',
	} as const;

	const OVERRIDE_MAP_TYPE = {
		CATEGORY: 'category',
		NORMAL: 'normal',
		TRANSACTIONAL: 'transactional',
	} as const;

	export const INTERCEPTORS = new Map<string, (body: JsonObject) => void>([
		[
			OVERRIDE_MAP_TYPE.CATEGORY,
			(body: JsonObject) => {
				body.type = OVERRIDE_MAP_VALUES.CATEGORY;
			},
		],
		[
			OVERRIDE_MAP_TYPE.NORMAL,
			(body: JsonObject) => {
				body.type = OVERRIDE_MAP_VALUES.NORMAL;
			},
		],
		[
			OVERRIDE_MAP_TYPE.TRANSACTIONAL,
			(body: JsonObject) => {
				body.type = OVERRIDE_MAP_VALUES.TRANSACTIONAL;
			},
		],
	]);
	export namespace Validators {
		function getFileName(
			itemIndex: number,
			mimeType: string,
			fileExt: string | undefined,
			fileName: string | undefined,
		): string {
			const ext = fileExt ?? mimeType.split('/')[1];

			if (!fileName) {
				return `file-${itemIndex}.${ext}`;
			}

			return fileName.toLowerCase().endsWith(`.${ext.toLowerCase()}`)
				? fileName
				: `${fileName}.${ext}`;
		}

		export async function validateAndCompileAttachmentsData(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const dataPropertyList = this.getNodeParameter(
				'additionalFields.emailAttachments.attachment',
			) as JsonObject;
			const { body } = requestOptions;

			const { attachment = [] } = body as { attachment: Array<{ content: string; name: string }> };

			try {
				const { binaryPropertyName } = dataPropertyList;
				const dataMappingList = (binaryPropertyName as string).split(',');
				for (const attachmentDataName of dataMappingList) {
					const binaryData = this.helpers.assertBinaryData(attachmentDataName.trim());
					const buffer = await this.helpers.getBinaryDataBuffer(attachmentDataName.trim());

					const content = buffer.toString(BINARY_ENCODING);
					const name = getFileName(
						this.getItemIndex(),
						binaryData.mimeType,
						binaryData.fileExtension,
						binaryData.fileName,
					);

					attachment.push({ content, name });
				}

				Object.assign(body!, { attachment });

				return requestOptions;
			} catch (err) {
				throw new NodeOperationError(this.getNode(), err as Error);
			}
		}

		export async function validateAndCompileTags(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const { tag } = this.getNodeParameter('additionalFields.emailTags.tags') as JsonObject;
			const tags = (tag as string)
				.split(',')
				.map((entry) => entry.trim())
				.filter((entry) => {
					return entry !== '';
				});
			const { body } = requestOptions;
			Object.assign(body!, { tags });
			return requestOptions;
		}

		function formatToEmailName(data: Address): Email {
			const { address: email, name } = data;
			const result = { email };
			if (name !== undefined && name !== '') {
				Object.assign(result, { name });
			}
			return { ...result };
		}

		function validateEmailStrings(input: ValidEmailFields): ValidatedEmail {
			const composer = new MailComposer({ ...input });
			const addressFields = composer.compile().getAddresses();

			const fieldFetcher = new Map<string, () => Email[] | Email>([
				[
					'bcc',
					() => {
						return (addressFields.bcc as unknown as Address[])?.map(formatToEmailName);
					},
				],
				[
					'cc',
					() => {
						return (addressFields.cc as unknown as Address[])?.map(formatToEmailName);
					},
				],
				[
					'from',
					() => {
						return (addressFields.from as unknown as Address[])?.map(formatToEmailName);
					},
				],
				[
					'reply-to',
					() => {
						return (addressFields['reply-to'] as unknown as Address[])?.map(formatToEmailName);
					},
				],
				[
					'sender',
					() => {
						return (addressFields.sender as unknown as Address[])?.map(formatToEmailName)[0];
					},
				],
				[
					'to',
					() => {
						return (addressFields.to as unknown as Address[])?.map(formatToEmailName);
					},
				],
			]);

			const result: { [key in keyof ValidatedEmail]: Email[] | Email } = {} as ValidatedEmail;
			Object.keys(input).reduce((obj: { [key: string]: Email[] | Email }, key: string) => {
				const getter = fieldFetcher.get(key);
				const value = getter!();
				obj[key] = value;
				return obj;
			}, result);

			return result as ValidatedEmail;
		}

		export async function validateAndCompileCCEmails(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const ccData = this.getNodeParameter(
				'additionalFields.receipientsCC.receipientCc',
			) as JsonObject;
			const { cc } = ccData;
			const { body } = requestOptions;
			const data = validateEmailStrings({ cc: cc as string });
			Object.assign(body!, data);

			return requestOptions;
		}

		export async function validateAndCompileBCCEmails(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const bccData = this.getNodeParameter(
				'additionalFields.receipientsBCC.receipientBcc',
			) as JsonObject;
			const { bcc } = bccData;
			const { body } = requestOptions;
			const data = validateEmailStrings({ bcc: bcc as string });
			Object.assign(body!, data);

			return requestOptions;
		}

		export async function validateAndCompileReceipientEmails(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const to = this.getNodeParameter('receipients') as string;
			const { body } = requestOptions;
			const data = validateEmailStrings({ to });
			Object.assign(body!, data);

			return requestOptions;
		}

		export async function validateAndCompileSenderEmail(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const sender = this.getNodeParameter('sender') as string;
			const { body } = requestOptions;
			const data = validateEmailStrings({ sender });
			Object.assign(body!, data);

			return requestOptions;
		}

		export async function validateAndCompileTemplateParameters(
			this: IExecuteSingleFunctions,
			requestOptions: IHttpRequestOptions,
		): Promise<IHttpRequestOptions> {
			const parameterData = this.getNodeParameter(
				'additionalFields.templateParameters.parameterValues',
			);
			const { body } = requestOptions;
			const { parameters } = parameterData as JsonObject;
			const params = (parameters as string)
				.split(',')
				.filter((parameter) => {
					return parameter.split('=').length === 2;
				})
				.map((parameter) => {
					const [key, value] = parameter.split('=');
					return {
						[key]: value,
					};
				})
				.reduce((obj, cObj) => {
					Object.assign(obj, cObj);
					return obj;
				}, {});

			Object.assign(body!, { params });
			return requestOptions;
		}
	}
}

export namespace BrevoWebhookApi {
	interface WebhookDetails {
		url: string;
		id: number;
		description: string;
		events: string[];
		type: string;
		createdAt: string;
		modifiedAt: string;
	}

	interface WebhookId {
		id: string;
	}

	interface Webhooks {
		webhooks: WebhookDetails[];
	}

	const credentialsName = 'sendInBlueApi';
	const baseURL = 'https://api.brevo.com/v3';
	export const supportedAuthMap = new Map<string, (ref: IWebhookFunctions) => Promise<string>>([
		[
			'apiKey',
			async (ref: IWebhookFunctions): Promise<string> => {
				const credentials = await ref.getCredentials(credentialsName);
				return credentials.sharedSecret as string;
			},
		],
	]);

	export const fetchWebhooks = async (ref: IHookFunctions, type: string): Promise<Webhooks> => {
		const endpoint = `${baseURL}/webhooks?type=${type}`;

		const options: IRequestOptions = {
			method: 'GET',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
		};

		const webhooks = (await ref.helpers.requestWithAuthentication.call(
			ref,
			credentialsName,
			options,
		)) as string;

		return await jsonParse(webhooks);
	};

	export const createWebHook = async (
		ref: IHookFunctions,
		type: string,
		events: string[],
		url: string,
	): Promise<WebhookId> => {
		const endpoint = `${baseURL}/webhooks`;

		const options: IRequestOptions = {
			method: 'POST',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
			body: {
				events,
				type,
				url,
			},
		};

		const webhookId = await ref.helpers.requestWithAuthentication.call(
			ref,
			credentialsName,
			options,
		);

		return await jsonParse(webhookId as string);
	};

	export const deleteWebhook = async (ref: IHookFunctions, webhookId: string) => {
		const endpoint = `${baseURL}/webhooks/${webhookId}`;
		const body = {};

		const options: IRequestOptions = {
			method: 'DELETE',
			headers: {
				Accept: 'application/json',
			},
			uri: endpoint,
			body,
		};

		return await ref.helpers.requestWithAuthentication.call(ref, credentialsName, options);
	};
}
