import { Injectable, Logger } from '@nestjs/common';
import { CreateJobDto } from './dto/create-job.dto';
import { UpdateJobDto } from './dto/update-job.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { calculateTotal } from 'utils/calculateTotal';
import { Prisma } from '@prisma/client';
import { equals } from 'class-validator';
import { createXeroInvoiceFromId } from 'xero/xeroHandler';
import { notEqual } from 'assert';
import e from 'express';
import { createStockTake } from 'utils/pdf/stockTake';

@Injectable()
export class JobsService {
	constructor(private prisma: PrismaService) {}
	private readonly logger = new Logger(JobsService.name);
	create(createJobDto: CreateJobDto) {
		const transaction = this.prisma.$transaction(async (tx) => {
			const job = await tx.job.create({
				data: {
					...createJobDto,
					contacts: {
						connect: createJobDto.contacts,
					},
					fees: {
						create: createJobDto.fees.map((fee) => ({
							quantity: fee.quantity,
							fee: {
								connect: {
									id: fee.feeId,
								},
							},
						})),
					},
				},
			});
			this.logger.log({ level: 'info', message: `Job created with ${JSON.stringify(createJobDto)}`, refCode: '21201', type: 'job', typeId: job.id });
			return job;
		});

		return transaction;
	}

	findAll(skip = 0, take = 10, sortBy: string, sortOrder: string, search: string = '', status: string | Array<string> | undefined = [], assignee: number | undefined = 0) {
		const transaction = this.prisma.$transaction(async (tx) => {
			// this.logger.log({ level: 'info', message: 'Returning all jobs', refCode: '21202' });
			if (take < 0) {
				take = 100;
			}
			let searchID = parseInt(search);

			if (Number.isNaN(searchID)) {
				searchID = 0;
			}

			if (sortBy == 'team') {
				sortBy = 'assignedToId';
			}

			// Default Query
			let query = {
				skip,
				take,
				orderBy: {
					[sortBy]: sortOrder,
				},
				include: {
					client: true,
					milestones: true,
					expenses: true,
					createdBy: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
					assignedTo: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
				},
				where: {},
			} as any;

			// Add Search Query to Where
			if (search) {
				query.where.OR = [{ id: { equals: searchID } }, { title: { contains: search } }, { quoteKey: { contains: search } }, { invoiceNumber: { contains: search } }, { client: { name: { contains: search } } }];
			}

			// Status Query
			let statusQuery = status;

			if (typeof status == 'string') {
				statusQuery = [status];
			}

			// Add Status Query to Where
			if (statusQuery && statusQuery.length > 0) {
				query.where.AND = [{ status: { in: statusQuery } }];
			} else {
				// query.where.AND = [{ status: { in: [ 'Accepted', 'In Progress', 'Completed', 'Invoiced' ] } }];
			}

			if (assignee && assignee > 0) {
				query.where.AND.push({ assignedToId: { equals: assignee } });
			}

			// Query
			var jobs = await tx.job.findManyAndCount(query);

			var customJobList = [];
			for (const job of jobs[0]) {
				var newJob: any = { ...job };

				newJob.totalItems = 0;
				newJob.totalAmount = 0;
				// @ts-ignore
				await calculateTotal(job.id, false, true).then((total) => {
					// @ts-ignore
					// this.logger.log({ level: 'info', message: `Total for job ${job.id} is ${JSON.stringify(total)}`, refCode: '21202' });
					if (total && total instanceof Object && !(total instanceof Array)) {
						newJob.totalItems = total.totalItems;
						newJob.totalAmount = total.totalAmount;
					}
				});

				customJobList.push(newJob);
			}
			jobs[0] = customJobList;
			return jobs;
		});
		return transaction;
	}

	findBySearch(skip = 0, take = 10, sortBy: string = 'id', sortOrder: string = 'asc', search: string | number = '', searchType: string = 'all') {
		const transaction = this.prisma.$transaction(async (tx) => {
			// Default Query
			let query = {
				skip,
				take,
				orderBy: {
					[sortBy]: sortOrder,
				},
				include: {
					client: true,
					createdBy: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
				},
			} as any;

			// Prepare Where Statement
			if (search) {
				query.where = {};
			}

			// construct search query

			if (searchType != undefined || searchType != '') {
				if (searchType.includes('.')) {
					var keyList = searchType.split('.');

					if (keyList[1] == 'id') {
						search = parseInt(search.toString());

						query.where.OR = [
							{
								[`${keyList[0]}`]: {
									[`${keyList[1]}`]: {
										equals: search,
									},
								},
							},
						];
					} else {
						query.where.OR = [
							{
								[`${keyList[0]}`]: {
									[`${keyList[1]}`]: {
										contains: search,
									},
								},
							},
						];
					}
				} else {
					query.where.OR = [
						{
							[`${searchType}`]: {
								contains: search,
							},
						},
					];
				}
			}

			console.log(query);

			// Query
			var jobs = await tx.job.findManyAndCount(query);

			return jobs;
		});
		return transaction;
	}

	findByMultiSearch(skip = 0, take = 10, sortBy: string = 'id', sortOrder: string = 'asc', includeTotals: boolean = true, search: string = '') {
		const transaction = this.prisma.$transaction(async (tx) => {
			// Default Query
			let query = {
				skip,
				take,
				orderBy: {
					[sortBy]: sortOrder,
				},
				include: {
					client: true,
					createdBy: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
					assignedTo: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
				},
			} as any;

			// Prepare Where Statement
			const searchJson: Array<{
				key: string;
				value: string;
				operator: string;
				condition: string;
			}> = search ? JSON.parse(search) : [];

			if (searchJson.length) {
				query.where = {};

				// Loop through search array
				searchJson.forEach((searchItem) => {
					const searchItemOperator = searchItem.operator ? searchItem.operator : 'equals';
					const searchItemAndOr = searchItem.condition ? searchItem.condition : 'AND';
					// const searchItemColumn: string | string[] = searchItem.key.includes('.') ? searchItem.key.split('.') : searchItem.key;
					const searchItemColumn: string | string[] = searchItem.key;
					let searchItemValue: string | number | Date = searchItem.value;

					/* 
                        Convert to Int 
                    */
					if (searchItem.key == 'id' || searchItem.key == 'clientId' || searchItem.key == 'productionPriority' || searchItem.key == 'createdById') {
						searchItemValue = parseInt(searchItem.value.toString());
					}

					/* 
                        Convert to Date 
                    */
					if (searchItem.key == 'createdAt' || searchItem.key == 'updatedAt' || searchItem.key == 'invoiceDate' || searchItem.key == 'productionDate') {
						searchItemValue = new Date(searchItem.value.toString());
					}

					/*
                        Prepare AndOr array if it doesn't exist 
                    */
					if (!query.where[searchItemAndOr]) {
						query.where[searchItemAndOr] = [];
					}

					/*
                        Add where clause to query
                    */
					if (typeof searchItemColumn == 'object') {
						query.where[searchItemAndOr].push({
							[`${searchItemColumn[0]}`]: {
								[`${searchItemColumn[1]}`]: {
									[`${searchItemOperator}`]: searchItemValue,
								},
							},
						});
					} else {
						query.where[searchItemAndOr].push({
							[`${searchItemColumn}`]: {
								[`${searchItemOperator}`]: searchItemValue,
							},
						});
					}
				});
			}

			// Query
			var jobs = await tx.job.findManyAndCount(query);

			if (includeTotals) {
				var customJobList = [];

				for (const job of jobs[0]) {
					var newJob: any = { ...job };

					newJob.totalItems = 0;
					newJob.totalAmount = 0;
					// @ts-ignore
					await calculateTotal(job.id, false, true).then((total) => {
						// @ts-ignore
						// this.logger.log({ level: 'info', message: `Total for job ${job.id} is ${JSON.stringify(total)}`, refCode: '21202' });
						if (total && total instanceof Object && !(total instanceof Array)) {
							newJob.totalItems = total.totalItems;
							newJob.totalAmount = total.totalAmount;
						}
					});

					customJobList.push(newJob);
				}

				jobs[0] = customJobList;
			}

			return jobs;
		});
		return transaction;
	}

	findOne(id: number, fields: Array<string> | undefined = []) {
		const transaction = this.prisma.$transaction(async (tx) => {
			// this.logger.log({ level: 'info', message: `Returning one job ${id}`, refCode: '21202' });

			// Full Query
			const fullQuery = {
				where: { id },
				include: {
					client: true,
					contacts: {
						select: {
							id: true,
							name: true,
							email: true,
							role: true,
						},
					},
					fees: {
						include: {
							fee: true,
						},
					},
					expenses: true,
					createdBy: {
						select: {
							id: true,
							email: true,
							name: true,
						},
					},
				},
			} as any;

			// Default Query
			let query = fullQuery;

			// Custom Query
			if (fields && fields.length) {
				query = {
					select: {},
					where: { id },
				};
				fields.forEach((field) => {
					if (fullQuery.include[field]) {
						query.select[field] = fullQuery.include[field];
					} else {
						query.select[field] = true;
					}
				});
			}

			return tx.job.findUnique(query);
		});
		return transaction;
	}

	findStatus(id: number) {
		const transaction = this.prisma.$transaction(async (tx) => {
			return tx.job.findUnique({
				select: {
					id: true,
					status: true,
				},
				where: { id },
			});
		});
		return transaction;
	}

	generateStockTake(jobId: number) {
		const transaction = this.prisma.$transaction(async (tx) => {
			this.logger.log({ level: 'info', message: `Creating stock take for job ${jobId}`, refCode: '21205', type: 'job', typeId: jobId });

			const resultURL = createStockTake(jobId);
			return resultURL;
		});
		return transaction;
	}

	update(id: number, updateJobDto: UpdateJobDto) {
		if (updateJobDto?.status == 'Invoiced' || updateJobDto?.status == 'Accepted') {
			updateJobDto.invoiceDate = new Date();
			createXeroInvoiceFromId(id);
		}

		const transaction = this.prisma.$transaction(async (tx) => {
			this.logger.log({ level: 'info', message: `Updating one job ${id} with ${JSON.stringify(updateJobDto)}`, refCode: '21203', type: 'job', typeId: id });
			return tx.job.update({
				where: { id },
				data: {
					...updateJobDto,
					contacts: {
						connect: updateJobDto.contacts,
					},
					fees: {
						upsert: updateJobDto.fees?.map((fee) => ({
							where: {
								id: fee.id,
							},
							update: {
								quantity: fee.quantity,
								fee: {
									connect: {
										id: fee.feeId,
									},
								},
							},
							create: {
								quantity: fee.quantity,
								fee: {
									connect: {
										id: fee.feeId,
									},
								},
							},
						})),
					},
				},
			});
		});
		return transaction;
	}

	remove(id: number) {
		const transaction = this.prisma.$transaction(async (tx) => {
			this.logger.log({ level: 'info', message: `Deleting one job ${id}`, refCode: '21204', type: 'job', typeId: id });
			return tx.job.delete({ where: { id } });
		});
		return transaction;
	}

	findQuote(quoteKey: string) {
		const transaction = this.prisma.$transaction(async (tx) => {
			// this.logger.log({ level: 'info', message: `Finding job with quote key ${quoteKey}`, refCode: '21202', type: 'system', typeId: 1 });
			return tx.job.findUnique({
				where: { quoteKey },
				include: {
					fees: {
						include: {
							fee: true,
						},
					},
				},
			});
		});
		return transaction;
	}

	findManyDates(startDate: string, endDate: string, statusString: string = 'all') {
		// this.logger.log({ level: 'info', message: `Finding all jobs with ${statusString}`, refCode: '21202', type: 'system', typeId: 1 });
		var status: any;
		if (statusString === 'all') {
			status = { status: { not: 'Complete' } };
		} else {
			status = { status: statusString };
		}

		const transaction = this.prisma.$transaction(async (tx) => {
			const defaultJoblist = await tx.job.findMany({
				where: {
					AND: [
						{
							invoiceDate: {
								gte: new Date(startDate),
							},
						},
						{
							invoiceDate: {
								lte: new Date(endDate),
							},
						},
						{ productionDate: null },
						status,
					],
				},
				include: {
					client: true,
					fees: {
						include: {
							fee: true,
						},
					},
					milestones: true,
					expenses: true,
				},
			});

			const assignedJoblist = await tx.job.findMany({
				where: {
					AND: [{ productionDate: { gte: new Date(startDate) } }, { productionDate: { lte: new Date(endDate) } }, { productionDate: { not: null } }, status],
				},
				include: {
					client: true,
					fees: {
						include: {
							fee: true,
						},
					},
					milestones: true,
					expenses: true,
				},
			});

			var joblist = [...defaultJoblist, ...assignedJoblist];
			// remove duplicates
			joblist = joblist.filter((job, index, self) => index === self.findIndex((t) => t.id === job.id));

			var customJobList = [];
			for (const job of joblist) {
				var newJob: any = { ...job };

				newJob.totalItems = 0;
				newJob.totalAmount = 0;

				await calculateTotal(job.id, false, true).then((total) => {
					// this.logger.log({ level: 'info', message: `Total for job ${job.id} is ${JSON.stringify(total)}`, refCode: '21202' });
					if (total && total instanceof Object && !(total instanceof Array)) {
						newJob.totalItems = total.totalItems;
						newJob.totalAmount = total.totalAmount;
					}
				});

				customJobList.push(newJob);
			}
			// sort by production priority
			customJobList = customJobList.sort((a, b) => a.productionPriority - b.productionPriority);

			// this.logger.log({ level: 'info', message: `Returning all (${customJobList.length}) jobs between ${startDate} and ${endDate}`, refCode: '21202' });
			return customJobList;
		});

		return transaction;
	}

	findAllSimpleTotal(client: string, status: string | Array<string> | undefined = []) {
		const transaction = this.prisma.$transaction(async (tx) => {
			// this.logger.log({ level: 'info', message: `Returning all clients`, refCode: '22202' });

			// Default Query
			const query = {
				select: {
					id: true,
				},
				where: {
					clientId: parseInt(client),
				},
			} as any;

			// Status Query
			let statusQuery = status;

			if (typeof status == 'string') {
				statusQuery = [status];
			}

			if (statusQuery.length > 0) {
				query.where = {
					AND: [{ clientId: parseInt(client) }, { status: { in: statusQuery } }],
				};
			}

			const list = tx.job.findMany(query);

			return (await list).length;
		});
		return transaction;
	}
}
