import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodePropertyOptions,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';

import {
	getFieldsObject,
	quickbaseApiRequest,
	quickbaseApiRequestAllItems,
} from './GenericFunctions';

import {
	fieldFields,
	fieldOperations,
} from './FieldDescription';

import {
	fileFields,
	fileOperations,
} from './FileDescription';

import {
	recordFields,
	recordOperations,
} from './RecordDescription';

import {
	reportFields,
	reportOperations,
} from './ReportDescription';

export class QuickBase implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Quick Base',
		name: 'quickbase',
		icon: 'file:quickbase.png',
		group: ['input'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Integrate with the Quick Base RESTful API.',
		defaults: {
			name: 'Quick Base',
			color: '#73489d',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'quickbaseApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Field',
						value: 'field',
					},
					{
						name: 'File',
						value: 'file',
					},
					{
						name: 'Record',
						value: 'record',
					},
					{
						name: 'Report',
						value: 'report',
					},
				],
				default: 'record',
				description: 'The resource to operate on.',
			},
			...fieldOperations,
			...fieldFields,
			...fileOperations,
			...fileFields,
			...recordOperations,
			...recordFields,
			...reportOperations,
			...reportFields,
		],
	};

	methods = {
		loadOptions: {
			async getTableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const tableId = this.getCurrentNodeParameter('tableId') as string;
				const fields = await quickbaseApiRequest.call(this, 'GET', '/fields', {}, { tableId });
				for (const field of fields) {
					returnData.push({
						name: field.label,
						value: field.id,
					});
				}
				return returnData;
			},
			async getUniqueTableFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const tableId = this.getCurrentNodeParameter('tableId') as string;
				const fields = await quickbaseApiRequest.call(this, 'GET', '/fields', {}, { tableId });
				for (const field of fields) {
					//upsert can be achived just with fields that are set as unique and are no the primary key
					if (field.unique === true && field.properties.primaryKey === false) {
						returnData.push({
							name: field.label,
							value: field.id,
						});
					}
				}
				return returnData;
			},
		},
	};

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

		if (resource === 'field') {
			if (operation === 'getAll') {
				for (let i = 0; i < length; i++) {

					const returnAll = this.getNodeParameter('returnAll', i) as boolean;

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

					const options = this.getNodeParameter('options', i) as IDataObject;

					const qs: IDataObject = {
						tableId,
					};

					Object.assign(qs, options);

					responseData = await quickbaseApiRequest.call(this, 'GET', '/fields', {}, qs);

					if (returnAll === false) {
						const limit = this.getNodeParameter('limit', i) as number;

						responseData = responseData.splice(0, limit);
					}

					returnData.push.apply(returnData, responseData);
				}
			}
		}

		if (resource === 'file') {
			if (operation === 'delete') {
				for (let i = 0; i < length; i++) {
					const tableId = this.getNodeParameter('tableId', i) as string;

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

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

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

					responseData = await quickbaseApiRequest.call(this, 'DELETE', `/files/${tableId}/${recordId}/${fieldId}/${versionNumber}`);

					returnData.push(responseData);
				}
			}

			if (operation === 'download') {

				for (let i = 0; i < length; i++) {

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

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

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

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

					const newItem: INodeExecutionData = {
						json: items[i].json,
						binary: {},
					};

					if (items[i].binary !== undefined) {
						// Create a shallow copy of the binary data so that the old
						// data references which do not get changed still stay behind
						// but the incoming data does not get changed.
						Object.assign(newItem.binary, items[i].binary);
					}

					items[i] = newItem;

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

					responseData = await quickbaseApiRequest.call(this, 'GET', `/files/${tableId}/${recordId}/${fieldId}/${versionNumber}`, {}, {}, { json: false, resolveWithFullResponse: true });

					//content-disposition': 'attachment; filename="dog-puppy-on-garden-royalty-free-image-1586966191.jpg"',
					const contentDisposition = responseData.headers['content-disposition'];

					const data = Buffer.from(responseData.body as string, 'base64');

					items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, contentDisposition.split('=')[1]);
				}

				return this.prepareOutputData(items);
			}
		}

		if (resource === 'record') {
			if (operation === 'create') {
				const tableId = this.getNodeParameter('tableId', 0) as string;

				const { fieldsLabelKey, fieldsIdKey } = await getFieldsObject.call(this, tableId);

				const simple = this.getNodeParameter('simple', 0) as boolean;

				const data: IDataObject[] = [];

				const options = this.getNodeParameter('options', 0) as IDataObject;

				for (let i = 0; i < length; i++) {
					const record: IDataObject = {};

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

					const columnList = columns.split(',').map(column => column.trim());

					for (const key of Object.keys(items[i].json)) {
						if (fieldsLabelKey.hasOwnProperty(key) && columnList.includes(key)) {
							record[fieldsLabelKey[key].toString()] = { value: items[i].json[key] };
						}
					}

					data.push(record);
				}

				const body: IDataObject = {
					data,
					to: tableId,
				};

				// If not fields are set return at least the record id
				body.fieldsToReturn = [fieldsLabelKey['Record ID#']];

				if (options.fields) {
					body.fieldsToReturn = options.fields as string[];
				}

				responseData = await quickbaseApiRequest.call(this, 'POST', '/records', body);

				if (simple === true) {
					const { data: records } = responseData;
					responseData = [];

					for (const record of records) {
						const data: IDataObject = {};
						for (const [key, value] of Object.entries(record)) {
							data[fieldsIdKey[key]] = (value as IDataObject).value;
						}
						responseData.push(data);
					}
				}

				if (Array.isArray(responseData)) {
					returnData.push.apply(returnData, responseData);
				} else {
					returnData.push(responseData);
				}
			}

			if (operation === 'delete') {
				for (let i = 0; i < length; i++) {
					const tableId = this.getNodeParameter('tableId', i) as string;

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

					const body: IDataObject = {
						from: tableId,
						where,
					};

					responseData = await quickbaseApiRequest.call(this, 'DELETE', '/records', body);

					returnData.push(responseData);
				}
			}

			if (operation === 'getAll') {
				for (let i = 0; i < length; i++) {

					const returnAll = this.getNodeParameter('returnAll', i) as boolean;

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

					const options = this.getNodeParameter('options', i) as IDataObject;

					const body: IDataObject = {
						from: tableId,
					};

					Object.assign(body, options);

					if (options.sortByUi) {
						const sort = (options.sortByUi as IDataObject).sortByValues as IDataObject[];
						body.sortBy = sort;
						delete body.sortByUi;
					}

					// if (options.groupByUi) {
					// 	const group = (options.groupByUi as IDataObject).groupByValues as IDataObject[];
					// 	body.groupBy = group;
					// 	delete body.groupByUi;
					// }

					if (returnAll) {
						responseData = await quickbaseApiRequestAllItems.call(this, 'POST', '/records/query', body, qs);
					} else {
						body.options = { top: this.getNodeParameter('limit', i) as number };

						responseData = await quickbaseApiRequest.call(this, 'POST', '/records/query', body, qs);

						const { data: records, fields } = responseData;
						responseData = [];

						const fieldsIdKey: { [key: string]: string } = {};

						for (const field of fields) {
							fieldsIdKey[field.id] = field.label;
						}

						for (const record of records) {
							const data: IDataObject = {};
							for (const [key, value] of Object.entries(record)) {
								data[fieldsIdKey[key]] = (value as IDataObject).value;
							}
							responseData.push(data);
						}
					}
					returnData.push.apply(returnData, responseData);
				}
			}

			if (operation === 'update') {
				const tableId = this.getNodeParameter('tableId', 0) as string;

				const { fieldsLabelKey, fieldsIdKey } = await getFieldsObject.call(this, tableId);

				const simple = this.getNodeParameter('simple', 0) as boolean;

				const updateKey = this.getNodeParameter('updateKey', 0) as string;

				const data: IDataObject[] = [];

				const options = this.getNodeParameter('options', 0) as IDataObject;

				for (let i = 0; i < length; i++) {
					const record: IDataObject = {};

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

					const columnList = columns.split(',').map(column => column.trim());

					for (const key of Object.keys(items[i].json)) {
						if (fieldsLabelKey.hasOwnProperty(key) && columnList.includes(key)) {
							record[fieldsLabelKey[key].toString()] = { value: items[i].json[key] };
						}
					}

					if (items[i].json[updateKey] === undefined) {
						throw new Error(`The update key ${updateKey} could not be found in the input`);
					}

					record[fieldsLabelKey['Record ID#']] = { value: items[i].json[updateKey] };

					data.push(record);
				}

				const body: IDataObject = {
					data,
					to: tableId,
				};

				// If not fields are set return at least the record id
				body.fieldsToReturn = [fieldsLabelKey['Record ID#']];

				if (options.fields) {
					body.fieldsToReturn = options.fields as string[];
				}

				responseData = await quickbaseApiRequest.call(this, 'POST', '/records', body);

				if (simple === true) {
					const { data: records } = responseData;
					responseData = [];

					for (const record of records) {
						const data: IDataObject = {};
						for (const [key, value] of Object.entries(record)) {
							data[fieldsIdKey[key]] = (value as IDataObject).value;
						}
						responseData.push(data);
					}
				}

				if (Array.isArray(responseData)) {
					returnData.push.apply(returnData, responseData);
				} else {
					returnData.push(responseData);
				}
			}

			if (operation === 'upsert') {
				const tableId = this.getNodeParameter('tableId', 0) as string;

				const { fieldsLabelKey, fieldsIdKey } = await getFieldsObject.call(this, tableId);

				const simple = this.getNodeParameter('simple', 0) as boolean;

				const updateKey = this.getNodeParameter('updateKey', 0) as string;

				const mergeFieldId = this.getNodeParameter('mergeFieldId', 0) as string;

				const data: IDataObject[] = [];

				const options = this.getNodeParameter('options', 0) as IDataObject;

				for (let i = 0; i < length; i++) {
					const record: IDataObject = {};

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

					const columnList = columns.split(',').map(column => column.trim());

					for (const key of Object.keys(items[i].json)) {
						if (fieldsLabelKey.hasOwnProperty(key) && columnList.includes(key)) {
							record[fieldsLabelKey[key].toString()] = { value: items[i].json[key] };
						}
					}

					if (items[i].json[updateKey] === undefined) {
						throw new Error(`The update key ${updateKey} could not be found in the input`);
					}

					record[mergeFieldId] = { value: items[i].json[updateKey] };

					data.push(record);
				}

				const body: IDataObject = {
					data,
					to: tableId,
					mergeFieldId,
				};

				// If not fields are set return at least the record id
				body.fieldsToReturn = [fieldsLabelKey['Record ID#']];

				if (options.fields) {
					body.fieldsToReturn = options.fields as string[];
				}

				responseData = await quickbaseApiRequest.call(this, 'POST', '/records', body);

				if (simple === true) {
					const { data: records } = responseData;
					responseData = [];

					for (const record of records) {
						const data: IDataObject = {};
						for (const [key, value] of Object.entries(record)) {
							data[fieldsIdKey[key]] = (value as IDataObject).value;
						}
						responseData.push(data);
					}
				}

				if (Array.isArray(responseData)) {
					returnData.push.apply(returnData, responseData);
				} else {
					returnData.push(responseData);
				}
			}
		}

		if (resource === 'report') {

			if (operation === 'run') {
				for (let i = 0; i < length; i++) {

					const returnAll = this.getNodeParameter('returnAll', i) as boolean;

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

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

					qs.tableId = tableId;

					if (returnAll) {
						responseData = await quickbaseApiRequestAllItems.call(this, 'POST', `/reports/${reportId}/run`, {}, qs);
					} else {
						qs.top = this.getNodeParameter('limit', i) as number;

						responseData = await quickbaseApiRequest.call(this, 'POST', `/reports/${reportId}/run`, {}, qs);

						const { data: records, fields } = responseData;
						responseData = [];

						const fieldsIdKey: { [key: string]: string } = {};

						for (const field of fields) {
							fieldsIdKey[field.id] = field.label;
						}

						for (const record of records) {
							const data: IDataObject = {};
							for (const [key, value] of Object.entries(record)) {
								data[fieldsIdKey[key]] = (value as IDataObject).value;
							}
							responseData.push(data);
						}
					}
					returnData.push.apply(returnData, responseData);
				}
			}

			if (operation === 'get') {
				for (let i = 0; i < length; i++) {

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

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

					qs.tableId = tableId;

					responseData = await quickbaseApiRequest.call(this, 'GET', `/reports/${reportId}`, {}, qs);

					returnData.push(responseData);
				}
			}
		}
		return [this.helpers.returnJsonArray(returnData)];
	}
}