import moment from 'moment-timezone';
import type {
	IExecuteFunctions,
	IDataObject,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodePropertyOptions,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';

import { contactFields, contactOperations } from './ContactDescription';
import {
	allFields,
	cleanData,
	googleApiRequest,
	googleApiRequestAllItems,
} from './GenericFunctions';

export class GoogleContacts implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Google Contacts',
		name: 'googleContacts',
		// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
		icon: 'file:googleContacts.png',
		group: ['input'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Consume Google Contacts API',
		defaults: {
			name: 'Google Contacts',
		},
		usableAsTool: true,
		inputs: [NodeConnectionTypes.Main],
		outputs: [NodeConnectionTypes.Main],
		credentials: [
			{
				name: 'googleContactsOAuth2Api',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				noDataExpression: true,
				options: [
					{
						name: 'Contact',
						value: 'contact',
					},
				],
				default: 'contact',
			},
			...contactOperations,
			...contactFields,
		],
	};

	methods = {
		loadOptions: {
			// Get all the calendars to display them to user so that they can
			// select them easily
			async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const groups = await googleApiRequestAllItems.call(
					this,
					'contactGroups',
					'GET',
					'/contactGroups',
				);
				for (const group of groups) {
					const groupName = group.name;
					const groupId = group.resourceName;
					returnData.push({
						name: groupName,
						value: groupId,
					});
				}
				return returnData;
			},
		},
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: INodeExecutionData[] = [];
		const length = items.length;
		const qs: IDataObject = {};
		let responseData;
		const resource = this.getNodeParameter('resource', 0);
		const operation = this.getNodeParameter('operation', 0);

		// Warmup cache
		// https://developers.google.com/people/v1/contacts#protocol_1
		if (resource === 'contact' && operation === 'getAll') {
			await googleApiRequest.call(this, 'GET', '/people:searchContacts', undefined, {
				query: '',
				readMask: 'names',
			});
			await googleApiRequest.call(this, 'GET', '/people/me/connections', undefined, {
				personFields: 'names',
			});
		}

		for (let i = 0; i < length; i++) {
			try {
				if (resource === 'contact') {
					//https://developers.google.com/calendar/v3/reference/events/insert
					if (operation === 'create') {
						const familyName = this.getNodeParameter('familyName', i) as string;
						const givenName = this.getNodeParameter('givenName', i) as string;
						const additionalFields = this.getNodeParameter('additionalFields', i);

						const body: IDataObject = {
							names: [
								{
									familyName,
									givenName,
									middleName: '',
								},
							],
						};

						if (additionalFields.middleName) {
							//@ts-ignore
							body.names[0].middleName = additionalFields.middleName as string;
						}

						if (additionalFields.honorificPrefix) {
							//@ts-ignore
							body.names[0].honorificPrefix = additionalFields.honorificPrefix as string;
						}

						if (additionalFields.honorificSuffix) {
							//@ts-ignore
							body.names[0].honorificSuffix = additionalFields.honorificSuffix as string;
						}

						if (additionalFields.companyUi) {
							const companyValues = (additionalFields.companyUi as IDataObject)
								.companyValues as IDataObject[];
							body.organizations = companyValues;
						}

						if (additionalFields.phoneUi) {
							const phoneValues = (additionalFields.phoneUi as IDataObject)
								.phoneValues as IDataObject[];
							body.phoneNumbers = phoneValues;
						}

						if (additionalFields.addressesUi) {
							const addressesValues = (additionalFields.addressesUi as IDataObject)
								.addressesValues as IDataObject[];
							body.addresses = addressesValues;
						}

						if (additionalFields.relationsUi) {
							const relationsValues = (additionalFields.relationsUi as IDataObject)
								.relationsValues as IDataObject[];
							body.relations = relationsValues;
						}

						if (additionalFields.eventsUi) {
							const eventsValues = (additionalFields.eventsUi as IDataObject)
								.eventsValues as IDataObject[];
							for (let index = 0; index < eventsValues.length; index++) {
								const [month, day, year] = moment(eventsValues[index].date as string)
									.format('MM/DD/YYYY')
									.split('/');
								eventsValues[index] = {
									date: {
										day,
										month,
										year,
									},
									type: eventsValues[index].type,
								};
							}
							body.events = eventsValues;
						}

						if (additionalFields.birthday) {
							const [month, day, year] = moment(additionalFields.birthday as string)
								.format('MM/DD/YYYY')
								.split('/');

							body.birthdays = [
								{
									date: {
										day,
										month,
										year,
									},
								},
							];
						}

						if (additionalFields.emailsUi) {
							const emailsValues = (additionalFields.emailsUi as IDataObject)
								.emailsValues as IDataObject[];
							body.emailAddresses = emailsValues;
						}

						if (additionalFields.biographies) {
							body.biographies = [
								{
									value: additionalFields.biographies,
									contentType: 'TEXT_PLAIN',
								},
							];
						}

						if (additionalFields.customFieldsUi) {
							const customFieldsValues = (additionalFields.customFieldsUi as IDataObject)
								.customFieldsValues as IDataObject[];
							body.userDefined = customFieldsValues;
						}

						if (additionalFields.group) {
							const memberships = (additionalFields.group as string[]).map((groupId: string) => {
								return {
									contactGroupMembership: {
										contactGroupResourceName: groupId,
									},
								};
							});

							body.memberships = memberships;
						}

						responseData = await googleApiRequest.call(
							this,
							'POST',
							'/people:createContact',
							body,
							qs,
						);

						responseData.contactId = responseData.resourceName.split('/')[1];
					}
					//https://developers.google.com/people/api/rest/v1/people/deleteContact
					if (operation === 'delete') {
						const contactId = this.getNodeParameter('contactId', i) as string;
						responseData = await googleApiRequest.call(
							this,
							'DELETE',
							`/people/${contactId}:deleteContact`,
							{},
						);
						responseData = { success: true };
					}
					//https://developers.google.com/people/api/rest/v1/people/get
					if (operation === 'get') {
						const contactId = this.getNodeParameter('contactId', i) as string;
						const fields = this.getNodeParameter('fields', i) as string[];
						const rawData = this.getNodeParameter('rawData', i);

						if (fields.includes('*')) {
							qs.personFields = allFields.join(',');
						} else {
							qs.personFields = fields.join(',');
						}

						responseData = await googleApiRequest.call(this, 'GET', `/people/${contactId}`, {}, qs);

						if (!rawData) {
							responseData = cleanData(responseData)[0];
						}

						responseData.contactId = responseData.resourceName.split('/')[1];
					}
					//https://developers.google.com/people/api/rest/v1/people.connections/list
					//https://developers.google.com/people/api/rest/v1/people/searchContacts
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);
						const fields = this.getNodeParameter('fields', i) as string[];
						const options = this.getNodeParameter('options', i, {});
						const rawData = this.getNodeParameter('rawData', i);
						const useQuery = this.getNodeParameter('useQuery', i) as boolean;

						const endpoint = useQuery ? ':searchContacts' : '/me/connections';

						if (useQuery) {
							qs.query = this.getNodeParameter('query', i) as string;
						}

						if (options.sortOrder) {
							qs.sortOrder = options.sortOrder as number;
						}

						if (fields.includes('*')) {
							qs.personFields = allFields.join(',');
						} else {
							qs.personFields = fields.join(',');
						}

						if (useQuery) {
							qs.readMask = qs.personFields;
							delete qs.personFields;
						}

						if (returnAll) {
							responseData = await googleApiRequestAllItems.call(
								this,
								useQuery ? 'results' : 'connections',
								'GET',
								`/people${endpoint}`,
								{},
								qs,
							);

							if (useQuery) {
								responseData = responseData.map((result: IDataObject) => result.person);
							}
						} else {
							qs.pageSize = this.getNodeParameter('limit', i);
							responseData = await googleApiRequest.call(this, 'GET', `/people${endpoint}`, {}, qs);

							responseData =
								responseData.connections ||
								responseData.results?.map((result: IDataObject) => result.person) ||
								[];
						}

						if (!rawData) {
							responseData = cleanData(responseData);
						}

						for (let index = 0; index < responseData.length; index++) {
							responseData[index].contactId = responseData[index].resourceName.split('/')[1];
						}
					}
					//https://developers.google.com/people/api/rest/v1/people/updateContact
					if (operation === 'update') {
						const updatePersonFields = [];

						const contactId = this.getNodeParameter('contactId', i) as string;

						const fields = this.getNodeParameter('fields', i) as string[];

						const updateFields = this.getNodeParameter('updateFields', i);

						let etag;

						if (updateFields.etag) {
							etag = updateFields.etag as string;
						} else {
							const data = await googleApiRequest.call(
								this,
								'GET',
								`/people/${contactId}`,
								{},
								{ personFields: 'Names' },
							);

							etag = data.etag;
						}

						if (fields.includes('*')) {
							qs.personFields = allFields.join(',');
						} else {
							qs.personFields = fields.join(',');
						}

						const body: IDataObject = {
							etag,
							names: [{}],
						};

						if (updateFields.givenName) {
							//@ts-ignore
							body.names[0].givenName = updateFields.givenName as string;
						}

						if (updateFields.familyName) {
							//@ts-ignore
							body.names[0].familyName = updateFields.familyName as string;
						}

						if (updateFields.middleName) {
							//@ts-ignore
							body.names[0].middleName = updateFields.middleName as string;
						}

						if (updateFields.honorificPrefix) {
							//@ts-ignore
							body.names[0].honorificPrefix = updateFields.honorificPrefix as string;
						}

						if (updateFields.honorificSuffix) {
							//@ts-ignore
							body.names[0].honorificSuffix = updateFields.honorificSuffix as string;
						}

						if (updateFields.companyUi) {
							const companyValues = (updateFields.companyUi as IDataObject)
								.companyValues as IDataObject[];
							body.organizations = companyValues;
							updatePersonFields.push('organizations');
						}

						if (updateFields.phoneUi) {
							const phoneValues = (updateFields.phoneUi as IDataObject)
								.phoneValues as IDataObject[];
							body.phoneNumbers = phoneValues;
							updatePersonFields.push('phoneNumbers');
						}

						if (updateFields.addressesUi) {
							const addressesValues = (updateFields.addressesUi as IDataObject)
								.addressesValues as IDataObject[];
							body.addresses = addressesValues;
							updatePersonFields.push('addresses');
						}

						if (updateFields.relationsUi) {
							const relationsValues = (updateFields.relationsUi as IDataObject)
								.relationsValues as IDataObject[];
							body.relations = relationsValues;
							updatePersonFields.push('relations');
						}

						if (updateFields.eventsUi) {
							const eventsValues = (updateFields.eventsUi as IDataObject)
								.eventsValues as IDataObject[];
							for (let index = 0; index < eventsValues.length; index++) {
								const [month, day, year] = moment(eventsValues[index].date as string)
									.format('MM/DD/YYYY')
									.split('/');
								eventsValues[index] = {
									date: {
										day,
										month,
										year,
									},
									type: eventsValues[index].type,
								};
							}
							body.events = eventsValues;
							updatePersonFields.push('events');
						}

						if (updateFields.birthday) {
							const [month, day, year] = moment(updateFields.birthday as string)
								.format('MM/DD/YYYY')
								.split('/');

							body.birthdays = [
								{
									date: {
										day,
										month,
										year,
									},
								},
							];

							updatePersonFields.push('birthdays');
						}

						if (updateFields.emailsUi) {
							const emailsValues = (updateFields.emailsUi as IDataObject)
								.emailsValues as IDataObject[];
							body.emailAddresses = emailsValues;
							updatePersonFields.push('emailAddresses');
						}

						if (updateFields.biographies) {
							body.biographies = [
								{
									value: updateFields.biographies,
									contentType: 'TEXT_PLAIN',
								},
							];
							updatePersonFields.push('biographies');
						}

						if (updateFields.customFieldsUi) {
							const customFieldsValues = (updateFields.customFieldsUi as IDataObject)
								.customFieldsValues as IDataObject[];
							body.userDefined = customFieldsValues;
							updatePersonFields.push('userDefined');
						}

						if (updateFields.group) {
							const memberships = (updateFields.group as string[]).map((groupId: string) => {
								return {
									contactGroupMembership: {
										contactGroupResourceName: groupId,
									},
								};
							});

							body.memberships = memberships;
							updatePersonFields.push('memberships');
						}

						if ((body.names as IDataObject[]).length > 0) {
							updatePersonFields.push('names');
						}

						qs.updatePersonFields = updatePersonFields.join(',');

						responseData = await googleApiRequest.call(
							this,
							'PATCH',
							`/people/${contactId}:updateContact`,
							body,
							qs,
						);

						responseData.contactId = responseData.resourceName.split('/')[1];
					}
				}

				const executionData = this.helpers.constructExecutionMetaData(
					this.helpers.returnJsonArray(responseData as IDataObject),
					{ itemData: { item: i } },
				);
				returnData.push(...executionData);
			} catch (error) {
				if (this.continueOnFail()) {
					const executionErrorData = this.helpers.constructExecutionMetaData(
						this.helpers.returnJsonArray({ error: error.message }),
						{ itemData: { item: i } },
					);
					returnData.push(...executionErrorData);
					continue;
				}
				throw error;
			}
		}

		return [returnData];
	}
}
