import { IExecuteFunctions } from 'n8n-core';
import {
	IDataObject,
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
	NodeApiError,
	NodeOperationError,
	NodeParameterValue,
} from 'n8n-workflow';


interface CustomProperty {
	name: string;
	value: string;
}

interface FilterValue {
	operation: string;
	value: NodeParameterValue;
}
interface FilterValues {
	[key: string]: FilterValue[];
}


export class Chargebee implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Chargebee',
		name: 'chargebee',
		icon: 'file:chargebee.png',
		group: ['input'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Retrieve data from Chargebee API',
		defaults: {
			name: 'Chargebee',
			color: '#22BB11',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'chargebeeApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Customer',
						value: 'customer',
					},
					{
						name: 'Invoice',
						value: 'invoice',
					},
					{
						name: 'Subscription',
						value: 'subscription',
					},
				],
				default: 'invoice',
				description: 'The resource to operate on.',
			},



			// ----------------------------------
			//         customer
			// ----------------------------------
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				displayOptions: {
					show: {
						resource: [
							'customer',
						],
					},
				},
				options: [
					{
						name: 'Create',
						value: 'create',
						description: 'Create a customer',
					},
				],
				default: 'create',
				description: 'The operation to perform.',
			},

			// ----------------------------------
			//         customer:create
			// ----------------------------------
			{
				displayName: 'Properties',
				name: 'properties',
				type: 'collection',
				displayOptions: {
					show: {
						operation: [
							'create',
						],
						resource: [
							'customer',
						],
					},
				},
				default: {},
				description: 'Properties to set on the new user',
				placeholder: 'Add Property',
				options: [
					{
						displayName: 'User Id',
						name: 'id',
						type: 'string',
						default: '',
						description: 'Id for the new customer. If not given, this will be auto-generated.',
					},
					{
						displayName: 'First Name',
						name: 'first_name',
						type: 'string',
						default: '',
						description: 'The first name of the customer.',
					},
					{
						displayName: 'Last Name',
						name: 'last_name',
						type: 'string',
						default: '',
						description: 'The last name of the customer.',
					},
					{
						displayName: 'Email',
						name: 'email',
						type: 'string',
						default: '',
						description: 'The email address of the customer.',
					},
					{
						displayName: 'Phone',
						name: 'phone',
						type: 'string',
						default: '',
						description: 'The phone number of the customer.',
					},
					{
						displayName: 'Company',
						name: 'company',
						type: 'string',
						default: '',
						description: 'The company of the customer.',
					},
					{
						displayName: 'Custom Properties',
						name: 'customProperties',
						placeholder: 'Add Custom Property',
						description: 'Adds a custom property to set also values which have not been predefined.',
						type: 'fixedCollection',
						typeOptions: {
							multipleValues: true,
						},
						default: {},
						options: [
							{
								name: 'property',
								displayName: 'Property',
								values: [
									{
										displayName: 'Property Name',
										name: 'name',
										type: 'string',
										default: '',
										description: 'Name of the property to set.',
									},
									{
										displayName: 'Property Value',
										name: 'value',
										type: 'string',
										default: '',
										description: 'Value of the property to set.',
									},
								],
							},
						],
					},

				],
			},



			// ----------------------------------
			//         invoice
			// ----------------------------------
			{
				displayName: 'Operation',
				name: 'operation',
				default: 'list',
				description: 'The operation to perform.',
				type: 'options',
				displayOptions: {
					show: {
						resource: [
							'invoice',
						],
					},
				},
				options: [
					{
						name: 'List',
						value: 'list',
						description: 'Return the invoices',
					},
					{
						name: 'PDF Invoice URL',
						value: 'pdfUrl',
						description: 'Get URL for the invoice PDF',
					},
				],
			},

			// ----------------------------------
			//         invoice:list
			// ----------------------------------
			{
				displayName: 'Max results',
				name: 'maxResults',
				type: 'number',
				typeOptions: {
					minValue: 1,
					maxValue: 100,
				},
				default: 10,
				displayOptions: {
					show: {
						operation: [
							'list',
						],
						resource: [
							'invoice',
						],
					},
				},
				description: 'Max. amount of results to return(< 100).',
			},
			{
				displayName: 'Filters',
				name: 'filters',
				placeholder: 'Add Filter',
				description: 'Filter for invoices.',
				type: 'fixedCollection',
				typeOptions: {
					multipleValues: true,
				},
				default: {},
				displayOptions: {
					show: {
						operation: [
							'list',
						],
						resource: [
							'invoice',
						],
					},
				},
				options: [
					{
						name: 'date',
						displayName: 'Invoice Date',
						values: [
							{
								displayName: 'Operation',
								name: 'operation',
								type: 'options',
								options: [
									{
										name: 'Is',
										value: 'is',
									},
									{
										name: 'Is Not',
										value: 'is_not',
									},
									{
										name: 'After',
										value: 'after',
									},
									{
										name: 'Before',
										value: 'before',
									},

								],
								default: 'after',
								description: 'Operation to decide where the the data should be mapped to.',
							},
							{
								displayName: 'Date',
								name: 'value',
								type: 'dateTime',
								default: '',
								description: 'Query date.',
							},
						],
					},
					{
						name: 'total',
						displayName: 'Invoice Amount',
						values: [
							{
								displayName: 'Operation',
								name: 'operation',
								type: 'options',
								options: [
									{
										name: 'Is',
										value: 'is',
									},
									{
										name: 'Is Not',
										value: 'is_not',
									},
									{
										name: 'Greater than',
										value: 'gt',
									},
									{
										name: 'Greater equal than',
										value: 'gte',
									},
									{
										name: 'Less than',
										value: 'lt',
									},
									{
										name: 'Less equal than',
										value: 'lte',
									},
								],
								default: 'gt',
								description: 'Operation to decide where the the data should be mapped to.',
							},
							{
								displayName: 'Amount',
								name: 'value',
								type: 'number',
								typeOptions: {
									numberPrecision: 2,
								},
								default: 0,
								description: 'Query amount.',
							},
						],
					},
				],
			},

			// ----------------------------------
			//         invoice:pdfUrl
			// ----------------------------------
			{
				displayName: 'Invoice Id',
				name: 'invoiceId',
				description: 'The id of the invoice to get.',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						operation: [
							'pdfUrl',
						],
						resource: [
							'invoice',
						],
					},
				},
			},



			// ----------------------------------
			//         subscription
			// ----------------------------------
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				displayOptions: {
					show: {
						resource: [
							'subscription',
						],
					},
				},
				options: [
					{
						name: 'Cancel',
						value: 'cancel',
						description: 'Cancel a subscription',
					},
					{
						name: 'Delete',
						value: 'delete',
						description: 'Delete a subscription',
					},
				],
				default: 'delete',
				description: 'The operation to perform.',
			},

			// ----------------------------------
			//         subscription:cancel
			// ----------------------------------
			{
				displayName: 'Subscription Id',
				name: 'subscriptionId',
				description: 'The id of the subscription to cancel.',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						operation: [
							'cancel',
						],
						resource: [
							'subscription',
						],
					},
				},
			},
			{
				displayName: 'Schedule end of Term',
				name: 'endOfTerm',
				type: 'boolean',
				default: false,
				displayOptions: {
					show: {
						operation: [
							'cancel',
						],
						resource: [
							'subscription',
						],
					},
				},
				description: 'If set it will not cancel it directly in will instead schedule the cancelation for the end of the term..',
			},

			// ----------------------------------
			//         subscription:delete
			// ----------------------------------
			{
				displayName: 'Subscription Id',
				name: 'subscriptionId',
				description: 'The id of the subscription to delete.',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						operation: [
							'delete',
						],
						resource: [
							'subscription',
						],
					},
				},
			},

		],
	};



	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		let item: INodeExecutionData;

		const credentials = this.getCredentials('chargebeeApi');

		if (credentials === undefined) {
			throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
		}

		const baseUrl = `https://${credentials.accountName}.chargebee.com/api/v2`;

		// For Post
		let body: IDataObject;
		// For Query string
		let qs: IDataObject;

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

			item = items[i];
			const resource = this.getNodeParameter('resource', i) as string;
			const operation = this.getNodeParameter('operation', i) as string;

			let requestMethod = 'GET';
			let endpoint = '';
			body = {};
			qs = {};
			if (resource === 'customer') {
				if (operation === 'create') {
					// ----------------------------------
					//         create
					// ----------------------------------

					requestMethod = 'POST';

					const properties = this.getNodeParameter('properties', i, {}) as IDataObject;

					for (const key of Object.keys(properties)) {
						if (key === 'customProperties' && (properties.customProperties as IDataObject).property !== undefined) {
							for (const customProperty of (properties.customProperties as IDataObject)!.property! as CustomProperty[]) {
								qs[customProperty.name] = customProperty.value;
							}
						} else {
							qs[key] = properties[key];
						}
					}

					endpoint = `customers`;
				} else {
					throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
				}

			} else if (resource === 'invoice') {
				if (operation === 'list') {
					// ----------------------------------
					//         list
					// ----------------------------------

					endpoint = 'invoices';
					// TODO: Make also sorting configurable
					qs['sort_by[desc]'] = 'date';

					qs.limit = this.getNodeParameter('maxResults', i, {});

					const setFilters: FilterValues = this.getNodeParameter('filters', i, {}) as unknown as FilterValues;

					let filter: FilterValue;
					let value: NodeParameterValue;

					for (const filterProperty of Object.keys(setFilters)) {
						for (filter of setFilters[filterProperty]) {
							value = filter.value;
							if (filterProperty === 'date') {
								value = Math.floor(new Date(value as string).getTime() / 1000);
							}
							qs[`${filterProperty}[${filter.operation}]`] = value;
						}
					}
				} else if (operation === 'pdfUrl') {
					// ----------------------------------
					//         pdfUrl
					// ----------------------------------

					requestMethod = 'POST';
					const invoiceId = this.getNodeParameter('invoiceId', i) as string;
					endpoint = `invoices/${invoiceId.trim()}/pdf`;
				} else {
					throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
				}

			} else if (resource === 'subscription') {
				if (operation === 'cancel') {
					// ----------------------------------
					//         cancel
					// ----------------------------------

					requestMethod = 'POST';

					const subscriptionId = this.getNodeParameter('subscriptionId', i, '') as string;
					body.end_of_term = this.getNodeParameter('endOfTerm', i, false) as boolean;

					endpoint = `subscriptions/${subscriptionId.trim()}/cancel`;
				} else if (operation === 'delete') {
					// ----------------------------------
					//         delete
					// ----------------------------------

					requestMethod = 'POST';

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

					endpoint = `subscriptions/${subscriptionId.trim()}/delete`;
				} else {
					throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
				}
			} else {
				throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
			}

			const options = {
				method: requestMethod,
				body,
				qs,
				uri: `${baseUrl}/${endpoint}`,
				auth: {
					user: credentials.apiKey as string,
					pass: '',
				},
				json: true,
			};

			let responseData;

			try {
				responseData = await this.helpers.request!(options);
			} catch (error) {
				throw new NodeApiError(this.getNode(), error);
			}

			if (resource === 'invoice' && operation === 'list') {
				responseData.list.forEach((data: IDataObject) => {
					returnData.push(data.invoice as IDataObject);
				});
			} else if (resource === 'invoice' && operation === 'pdfUrl') {
				const data: IDataObject = {};
				Object.assign(data, items[i].json);

				data.pdfUrl = responseData.download.download_url;
				returnData.push(data);
			} else {
				returnData.push(responseData);
			}
		}

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