import { Contact, Contacts, Invoice, Invoices, Item, LineAmountTypes, LineItem, XeroClient } from 'xero-node';
import { PrismaService } from 'nestjs-prisma';

const client_id: string = process.env.CLIENT_ID ?? '';
const client_secret: string = process.env.CLIENT_SECRET ?? '';
const grant_type: string = 'client_credentials';

// secret stuff
const xero = new XeroClient({
	clientId: client_id,
	clientSecret: client_secret,
	grantType: grant_type,
});

const xeroTenantId = '';
const summarizeErrors = true;
const unitdp = 4;

const prisma = new PrismaService();

if (!client_id || !client_secret) {
	throw Error('Environment Variables not all set - please check your .env file in the project root or create one!');
}

export async function createXeroInvoiceFromQuote(quoteKey: string) {
	await xero.getClientCredentialsToken();

	const availableJob = await prisma.job.findUnique({
		where: { quoteKey },
		include: {
			client: true,
			contacts: {
				select: {
					id: true,
					name: true,
					email: true,
					role: true,
				},
			},
			fees: {
				include: {
					fee: true,
				},
			},
			expenses: true,
		},
	});

	if (availableJob) {
		createXeroInvoice(availableJob, false);
	}
}

export async function createXeroInvoiceFromId(jobId: number) {
	await xero.getClientCredentialsToken();

	const availableJob = await prisma.job.findUnique({
		where: { id: jobId },
		include: {
			client: true,
			contacts: {
				select: {
					id: true,
					name: true,
					email: true,
					role: true,
				},
			},
			fees: {
				include: {
					fee: true,
				},
			},
			expenses: true,
		},
	});

	if (availableJob) {
		createXeroInvoice(availableJob, false);
	}
}

export async function createXeroInvoice(availableJob: any, wholeInvoice = false) {
	if (availableJob) {
		// ---------------------------------------------------------
		// all client stuff
		// ---------------------------------------------------------
		const clientObject = availableJob.client;
		const where = 'name == "' + clientObject.name + '"';
		var client: Contact = {};

		try {
			const response = await xero.accountingApi.getContacts('', undefined, where);
			if (response.body.contacts !== undefined && response.body.contacts?.length > 0) {
				client = response.body.contacts[0];
			} else {
				// if no contact create
				const xeroClient: Contact = {
					name: clientObject.name,
					emailAddress: clientObject.email,
				};

				const contacts: Contacts = {
					contacts: [xeroClient],
				};

				try {
					const response = await xero.accountingApi.createContacts(xeroTenantId, contacts, summarizeErrors);
					if (response.body.contacts != undefined) {
						client = response.body.contacts[0];
					}
				} catch (err: any) {
					const error = JSON.stringify(err.response.body, null, 2);
					console.log(`Status Code: ${err.response.statusCode} => ${error}`);
				}
			}
		} catch (err: any) {
			const error = JSON.stringify(err.response, null, 2);
			console.log(`Status Code: xxx => ${error}`);
			//  ${err.response.statusCode}
		}

		// ---------------------------------------------------------
		// Item stuff
		// ---------------------------------------------------------
		var items: Item[] = [];
		const orderItems = 'Code ASC';
		try {
			const response = await xero.accountingApi.getItems(xeroTenantId, undefined, '', orderItems, unitdp);
			if (response.body.items != undefined) {
				items = response.body.items;
			}
		} catch (err: any) {
			const error = JSON.stringify(err.response, null, 2);
			console.log(`Status Code: xxx => ${error}`);
		}

		var lineItems: LineItem[] = [];

		// ---------------------------------------------------------
		// gather designs
		// ---------------------------------------------------------

		const availableDesigns = await prisma.design.findMany({
			where: {
				jobId: availableJob.id,
			},
			include: {
				lineItems: {
					include: {
						product: true,
					},
				},
				imprints: {
					include: {
						inks: true,
					},
				},
			},
		});

		// get design price add to lineItems

		for (const design of availableDesigns) {
			// get imprint pricetype

			const priceTypeArray = design.imprints.map((imprint) => imprint.pricingType);

			// get inkTotal

			const inkTotalArray = design.imprints.map((imprint) => imprint.inks.length);

			var combinedTotal = 0;
			design.lineItems.forEach((lineItem: any) => {
				combinedTotal += lineItem.total;
			});

			var printPrice: number = 0;

			for (const [index, priceType] of priceTypeArray.entries()) {
				if (priceType === 'Primary') {
					const matrix = await prisma.pricingmatrix.findFirst({
						orderBy: {
							quantity: 'desc',
						},
						where: {
							quantity: {
								lte: combinedTotal,
							},
							columnName: `colour${inkTotalArray[index]}`,
							tableName: 'primary',
							type: 'screen printing',
						},
					});
					if (matrix) {
						printPrice += matrix.amount as number;
					}
				} else if (priceType === 'Secondary') {
					const matrix = await prisma.pricingmatrix.findFirst({
						orderBy: {
							quantity: 'desc',
						},
						where: {
							quantity: {
								lte: combinedTotal,
							},
							columnName: `colour${inkTotalArray[index]}`,
							tableName: 'secondary',
							type: 'screen printing',
						},
					});
					if (matrix) {
						printPrice += matrix.amount as number;
					}
				} else if (priceType === 'Tertiary') {
					const matrix = await prisma.pricingmatrix.findFirst({
						orderBy: {
							quantity: 'desc',
						},
						where: {
							quantity: {
								lte: combinedTotal,
							},
							columnName: `colour${inkTotalArray[index]}`,
							tableName: 'tertiary',
							type: 'screen printing',
						},
					});
					if (matrix) {
						printPrice += matrix.amount as number;
					}
				} else {
					throw new Error('Invalid price type');
				}
			}

			design.lineItems.forEach((lineItem: any) => {
				var item: Item = items.find((item) => item.code === lineItem.product.brandId) ?? new Item();

				var finalPrice = 0;

				if (lineItem.price) {
					finalPrice = lineItem.price + printPrice;
				} else {
					finalPrice = lineItem.product.price + printPrice;
				}

				var invoiceLineItem: LineItem = {
					itemCode: item.code,
					description: lineItem.product.description,
					quantity: lineItem.total,
					accountCode: item.salesDetails?.accountCode ?? '4050',
					unitAmount: finalPrice,
					taxType: item.salesDetails?.taxType ?? 'OUTPUT',
				};
				lineItems.push(invoiceLineItem);
			});
		}

		availableJob.fees.forEach((fee: any) => {
			console.log(fee);
			var item: Item = items.find((item) => item.code === fee.feeId) ?? new Item();
			var lineItem: LineItem = {
				description: fee.fee.description,
				quantity: fee.quantity,
				accountCode: item.salesDetails?.accountCode ?? '4050',
				unitAmount: fee.priceOverwrite ? fee.priceOverwrite : fee.fee.price,
				taxType: item.salesDetails?.taxType ?? 'OUTPUT',
			};
			lineItems.push(lineItem);
		});

		// create invoice

		const tempDate1 = new Date(availableJob.invoiceDate);
		var tempDate2 = new Date(availableJob.invoiceDate);
		tempDate2.setDate(tempDate2.getDate() + 14);

		var invoice: Invoice = {
			type: Invoice.TypeEnum.ACCREC,
			contact: {
				contactID: client.contactID,
			},
			date: tempDate1.toISOString().split('T')[0],
			dueDate: tempDate2.toISOString().split('T')[0],
			lineItems: lineItems,
			status: Invoice.StatusEnum.DRAFT,
			lineAmountTypes: LineAmountTypes.Exclusive,
			invoiceNumber: availableJob.id + '-1',
			reference: availableJob.title,
		};

		// check if 50% invoice exists and then add credit for that amount

		const invoiceNumber = availableJob.id + '-1';
		const where2 = 'invoiceNumber == "' + invoiceNumber + '"';

		try {
			const response = await xero.accountingApi.getInvoices(xeroTenantId, undefined, where2);
			if (response.body.invoices !== undefined && response.body.invoices.length > 0) {
				const oldInvoice = response.body.invoices[0];
				const newLineItem: LineItem = {
					description: 'previous deposit',
					quantity: 1,
					accountCode: '4050',
					unitAmount: -1 * ((oldInvoice.total ?? 0) / 1.1),
					taxType: 'OUTPUT',
				};
				invoice.lineItems?.push(newLineItem);

				console.log('50% invoice already exists, creating second half invoice');
				console.log(invoice.lineItems);
				invoice.invoiceNumber = availableJob.id + '-2';
			} else {
				// run new invoice protocol
				if (!wholeInvoice) {
					const lineItemToHalf = invoice.lineItems;
					var subtotal = 0;
					if (lineItemToHalf) {
						for (const lineItem of lineItemToHalf) {
							subtotal += (lineItem.quantity ?? 0) * (lineItem.unitAmount ?? 0);
						}
					}

					const newLineItem: LineItem = {
						description: '50% deposit',
						quantity: 1,
						accountCode: '4050',
						unitAmount: subtotal / 2,
						taxType: 'OUTPUT',
					};
					lineItems = [newLineItem];
					invoice.lineItems = lineItems;
				}
			}
		} catch (err: any) {
			const error = JSON.stringify(err.response.body, null, 2);
			console.log(`Status Code: ${err.response.statusCode} => ${error}`);
		}

		const invoices: Invoices = {
			invoices: [invoice],
		};

		try {
			const response = await xero.accountingApi.createInvoices(xeroTenantId, invoices, summarizeErrors, unitdp);
			return response.body.invoices;
		} catch (err: any) {
			const err1 = JSON.parse(err);
			const error = JSON.parse(JSON.stringify(err1.response.body, null, 2));
			console.log(`Invoice ${error.Elements[0].InvoiceNumber} Status Code: ${err1.response.statusCode} => ${error.Elements[0].ValidationErrors[0].Message}`);
		}
	}
}

export async function findInvoiceByJobId(jobId: number) {
	await xero.getClientCredentialsToken();

	const invoiceNumber = jobId + '-1';
	const where = 'invoiceNumber == "' + invoiceNumber + '"';

	var invoiceArray = [];

	try {
		const response = await xero.accountingApi.getInvoices(xeroTenantId, undefined, where);
		if (response.body.invoices !== undefined && response.body.invoices.length > 0) {
			invoiceArray.push(response.body.invoices);
		}
	} catch (err: any) {
		const error = JSON.stringify(err.response.body, null, 2);
		console.log(`Status Code: ${err.response.statusCode} => ${error}`);
	}

	if (invoiceArray.length > 0) {
		const invoiceNumber = jobId + '-2';
		const where = 'invoiceNumber == "' + invoiceNumber + '"';

		try {
			const response = await xero.accountingApi.getInvoices(xeroTenantId, undefined, where);
			if (response.body.invoices !== undefined && response.body.invoices.length > 0) {
				invoiceArray.push(response.body.invoices);
			}
		} catch (err: any) {
			const error = JSON.stringify(err.response.body, null, 2);
			console.log(`Status Code: ${err.response.statusCode} => ${error}`);
		}
	}

	return invoiceArray;
}

export async function findInvoicesByJobIds(ids: number[]) {
	await xero.getClientCredentialsToken();

	const where = ids.map((id) => `InvoiceNumber == "${id}-1" OR InvoiceNumber == "${id}-2"`).join(' OR ');

	var invoiceArray = [];

	try {
		const response = await xero.accountingApi.getInvoices(xeroTenantId, undefined, where);
		if (response.body.invoices !== undefined && response.body.invoices.length > 0) {
			invoiceArray.push(response.body.invoices);
		}
	} catch (err: any) {
		// unstringify to get as string is returned
		const error = JSON.parse(err);
		console.log(error);

		console.log(`Status Code: ${error.response.statusCode ?? 'unknown'} => ${error.response.body.Title ?? error}`);
	}

	return invoiceArray;
}

export async function findPaymentsByJobId(jobId: number) {
	await xero.getClientCredentialsToken();

	const where = 'Invoice.InvoiceNumber == "' + jobId + '-1" OR Invoice.InvoiceNumber == "' + jobId + '-2" OR Invoice.InvoiceNumber == "' + jobId + '"';

	var paymentArray = [];

	try {
		const response = await xero.accountingApi.getPayments(xeroTenantId, undefined, where);
		if (response.body.payments !== undefined && response.body.payments.length > 0) {
			paymentArray.push(response.body.payments);
		}
	} catch (err: any) {
		// unstringify to get as string is returned
		const error = JSON.parse(err);
		console.log(error);

		console.log(`Status Code: ${error.response.statusCode ?? 'unknown'} => ${error.response.body.Title ?? error}`);
	}

	return paymentArray;
}

export async function findPaymentsByJobIds(ids: number[]) {
	await xero.getClientCredentialsToken();

	const where = ids.map((id) => `Invoice.InvoiceNumber == "${id}-1" OR Invoice.InvoiceNumber == "${id}-2"`).join(' OR ');

	var paymentArray = [];

	try {
		const response = await xero.accountingApi.getPayments(xeroTenantId, undefined, where);
		if (response.body.payments !== undefined && response.body.payments.length > 0) {
			paymentArray.push(response.body.payments);
		}
	} catch (err: any) {
		// unstringify to get as string is returned
		const error = JSON.parse(err);
		console.log(error);
		console.log(`Status Code: ${err.response.statusCode ?? 'unknown'} => ${error.response.body.Title ?? error}`);
	}

	return paymentArray;
}
