import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from 'nestjs-prisma';
import { CreateDesignDto } from 'src/designs/dto/create-design.dto';
import { UpdateDesignDto } from 'src/designs/dto/update-design.dto';
import { CreateImprintDto } from 'src/imprints/dto/create-imprint.dto';
import { UpdateImprintDto } from 'src/imprints/dto/update-imprint.dto';
import { CreateJobDto } from 'src/jobs/dto/create-job.dto';
import { UpdateJobDto } from 'src/jobs/dto/update-job.dto';
import { CreateLineitemDto } from 'src/lineitems/dto/create-lineitem.dto';
import { UpdateLineitemDto } from 'src/lineitems/dto/update-lineitem.dto';
import { CreateMilestoneDto } from 'src/milestones/dto/create-milestone.dto';
import { UpdateMilestoneDto } from 'src/milestones/dto/update-milestone.dto';

import fs from 'fs';
import { Milestone } from 'src/milestones/entities/milestone.entity';
import { DesignEntity } from 'src/designs/entities/design.entity';
import { Expense } from 'src/expenses/entities/expense.entity';

@Injectable()
export class CombinesService {
	constructor(private prisma: PrismaService) {}
	private readonly logger = new Logger(CombinesService.name);

	async createFullJob(fullBodyJob: { job: any; milestones: [Milestone]; designs: [DesignEntity]; expenses: [Expense] }) {
		const clientId = fullBodyJob.job.clientId;
		const createdById = 1; // fullBodyJob.job.createdById;
		const assignedToId = fullBodyJob.job.assignedToId;

		delete fullBodyJob.job.clientId;
		delete fullBodyJob.job.createdById;
		delete fullBodyJob.job.assignedToId;

		const createJobDto: CreateJobDto = {
			...fullBodyJob.job,
		};
		const transaction = await this.prisma.$transaction(async (tx: any) => {
			const returnJob = await tx.job.create({
				data: {
					...createJobDto,
					client: {
						connect: {
							id: clientId,
						},
					},
					createdBy: {
						connect: {
							id: createdById,
						},
					},
					assignedTo: {
						connect: {
							id: assignedToId,
						},
					},
					contacts: {
						connect: createJobDto.contacts,
					},
					fees: {
						create: createJobDto.fees.map((fee) => ({
							quantity: fee.quantity,
							priceOverwrite: fee.priceOverwrite,
							fee: {
								connect: {
									id: fee.feeId,
								},
							},
						})),
					},
				},
			});

			this.logger.log({ level: 'info', message: `Creating job ${JSON.stringify(returnJob)}`, refCode: '33201', type: 'job', typeId: returnJob.id });

			const milestones: [any] = fullBodyJob.milestones;
			for (const milestone of milestones) {
				try {
					delete milestone.id;
					const baseMilestone: CreateMilestoneDto = { ...milestone, jobId: returnJob.id };
					const returnMilestone = await tx.milestone.create({ data: baseMilestone });
					this.logger.log({ level: 'info', message: `Creating milestone ${JSON.stringify(returnMilestone)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
				} catch (error) {
					this.logger.error(`Error creating milestone: ${error}`);
					throw new Error(`Error creating milestone: ${error}`);
				}
			}

			const designs: [any] = fullBodyJob.designs;

			for (const design of designs) {
				var tempID = design.id;
				try {
					delete design.id;
					const baseDesign: CreateDesignDto = { name: design.name, jobId: returnJob.id };
					const returnDesign = await tx.design.create({ data: baseDesign });
					tempID = returnDesign.id;
					this.logger.log({ level: 'info', message: `Creating design ${JSON.stringify(returnDesign)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
				} catch (error) {
					this.logger.error(`Error creating design: ${error}`);
					throw new Error(`Error creating design: ${error}`);
				}

				const products = design.lineItems;
				for (const product of products) {
					try {
						delete product.id;
						delete product.product;
						delete product.category;
						const lineItem: CreateLineitemDto = { ...product, designId: tempID, price: product.price };

						const sizes = product.sizes.map((size: { name: any; amount: any }) => ({
							name: size.name,
							amount: size.amount,
						}));

						delete lineItem.sizes;
						const returnLineItem = await tx.lineitem.create({ data: lineItem });
						await tx.size.createMany({
							data: sizes.map((size: { name: any; amount: any }) => ({
								lineItemId: returnLineItem.id,
								name: size.name,
								amount: size.amount,
							})),
						});

						this.logger.log({ level: 'info', message: `Creating line item ${JSON.stringify(returnLineItem)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
					} catch (error) {
						this.logger.error(`Error creating line item: ${error}`);
						throw new Error(`Error creating line item: ${error}`);
					}
				}

				const imprints = design.imprints;

				for (const imprint of imprints) {
					const inks = imprint.inks;

					var newInks = [];
					for (const ink of inks) {
						newInks.push(ink.imprint);
					}

					var imageName = '';

					this.logger.log({ level: 'info', message: `Creating imprint ${JSON.stringify(imprint.images)}`, refCode: '33201', type: 'job', typeId: returnJob.id });

					try {
						delete imprint.id;
						const baseImprint: CreateImprintDto = { ...imprint, designId: tempID, inks: newInks };

						const returnImprint = await tx.imprint.create({
							data: {
								type: baseImprint.type,
								location: baseImprint.location,
								designId: baseImprint.designId,
								pricingType: baseImprint.pricingType,
								images: {
									create: baseImprint.images.map((image) => ({
										url: image.url,
									})),
								},
								height: baseImprint.height,
								width: baseImprint.width,
								inks: {
									create: baseImprint.inks.map((ink) => ({
										mesh: ink.mesh,
										ink: {
											connect: {
												id: ink.inkId,
											},
										},
									})),
								},
							},
						});
						this.logger.log({ level: 'info', message: `Creating imprint ${JSON.stringify(returnImprint)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
					} catch (error) {
						this.logger.error(`Error creating imprint: ${error}`);
						throw new Error(`Error creating imprint: ${error}`);
					}
				}
			}
			return returnJob;
		});
		return transaction;
	}

	async updateFullJob(id: number, fullBodyJob: { job: any; milestones: [any]; designs: [any]; expenses: [any] }) {
		const currentTime = new Date().getTime();
		const transaction = await this.prisma.$transaction(async (tx) => {
			delete fullBodyJob.job.id;
			delete fullBodyJob.job.createdById;
			delete fullBodyJob.job.client;

			// if status == quote regen quote key
			if (fullBodyJob.job.status == 'Quote' && !fullBodyJob.job.quoteKey) {
				const quoteKey = Math.random().toString(36).substring(7);
				fullBodyJob.job.quoteKey = quoteKey;
			}

			const updateJobDto: UpdateJobDto = {
				...fullBodyJob.job,
			};

			// log fee
			if (updateJobDto.fees) {
				updateJobDto.fees.forEach((fee) => {
					this.logger.log({ level: 'info', message: `Fee to be updated: ${JSON.stringify(fee)}`, refCode: '33201', type: 'job', typeId: id });
				});
			}

			const returnJob = await tx.job.update({
				where: { id },
				data: {
					...updateJobDto,
					contacts: {
						set: updateJobDto.contacts,
					},
					fees: {
						deleteMany: {},
						upsert: updateJobDto.fees.map((fee) => ({
							where: {
								id: fee.id ?? 0,
							},
							update: {
								quantity: fee.quantity,
								priceOverwrite: fee.priceOverwrite,
								fee: {
									connect: {
										id: fee.feeId,
									},
								},
							},
							create: {
								quantity: fee.quantity,
								priceOverwrite: fee.priceOverwrite,
								fee: {
									connect: {
										id: fee.feeId,
									},
								},
							},
						})),
					},
				},
			});

			// check if it current job was updated in the last 1000ms
			if (currentTime - returnJob.updatedAt.getTime() < 1000) {
				this.logger.log({ level: 'info', message: `Job ${returnJob.id} was updated with ${JSON.stringify(returnJob)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
			}

			const milestones: [any] = fullBodyJob.milestones;
			for (const milestone of milestones) {
				try {
					if (milestone.id < 1) {
						delete milestone.id;
						const baseMilestone: CreateMilestoneDto = { ...milestone, jobId: returnJob.id };
						const returnMilestone = await tx.milestone.create({ data: baseMilestone });
						this.logger.log({ level: 'info', message: `Creating milestone ${JSON.stringify(returnMilestone)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
					} else {
						const baseMilestone: UpdateMilestoneDto = { ...milestone, jobId: returnJob.id };
						const returnMilestone = await tx.milestone.update({ where: { id: milestone.id }, data: baseMilestone });
						if (currentTime - returnMilestone.updatedAt.getTime() < 1000) {
							this.logger.log({ level: 'info', message: `Updating milestone ${JSON.stringify(returnMilestone)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						}
					}
				} catch (error) {
					this.logger.error(`Error creating milestone: ${error}`);
					throw new Error(`Error creating milestone: ${error}`);
				}

				// throw error here
			}

			const designs: [any] = fullBodyJob.designs;
			for (const design of designs) {
				var tempID = design.id ?? 0;
				try {
					if (design.id < 4 || !design.hasOwnProperty('id')) {
						delete design.id;
						const baseDesign: CreateDesignDto = { name: design.name, jobId: returnJob.id };
						const returnDesign = await tx.design.create({ data: baseDesign });
						tempID = returnDesign.id;
						this.logger.log({ level: 'info', message: `Creating design ${JSON.stringify(returnDesign)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
					} else {
						const baseDesign: UpdateDesignDto = { name: design.name, jobId: returnJob.id };
						const returnDesign = await tx.design.update({ where: { id: tempID }, data: baseDesign });
						if (currentTime - returnDesign.updatedAt.getTime() < 1000) {
							this.logger.log({ level: 'info', message: `Updating design ${JSON.stringify(returnDesign)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						}
					}
				} catch (error) {
					this.logger.error(`Error creating design: ${error}`);
					throw new Error(`Error creating design: ${error}`);
				}

				const products = design.lineItems;
				for (const product of products) {
					try {
						if (product.id < 4 || product.id == undefined || product.id == null || !product.hasOwnProperty('id')) {
							delete product.id;
							const lineItem: CreateLineitemDto = { ...product, designId: tempID, price: product.price };

							const sizes = product.sizes.map((size: { name: any; amount: any }) => ({
								name: size.name,
								amount: size.amount,
							}));

							delete lineItem.sizes;
							const returnLineItem = await tx.lineitem.create({ data: lineItem });
							await tx.size.createMany({
								data: sizes.map((size: { name: any; amount: any }) => ({
									lineItemId: returnLineItem.id,
									name: size.name,
									amount: size.amount,
								})),
							});
							this.logger.log({ level: 'info', message: `Creating line item ${JSON.stringify(returnLineItem)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						} else {
							const itemId = product.id;
							delete product.id;
							delete product.productId;
							delete product.designId;
							delete product.product;
							delete product.category;
							const lineItem: UpdateLineitemDto = { ...product, designId: tempID, price: product.price };

							const sizes = product.sizes.map((size: { name: any; amount: any }) => ({
								name: size.name,
								amount: parseInt(size.amount),
							}));

							delete lineItem.sizes;

							await tx.size.deleteMany({
								where: {
									lineItemId: itemId,
								},
							});
							await tx.size.createMany({
								data: sizes.map((size: { name: any; amount: any }) => ({
									lineItemId: itemId,
									name: size.name,
									amount: size.amount,
								})),
							});

							const returnLineItem = await tx.lineitem.update({
								where: { id: itemId },
								data: {
									...lineItem,
								},
							});
							if (currentTime - returnLineItem.updatedAt.getTime() < 1000) {
								this.logger.log({ level: 'info', message: `Updating line item ${JSON.stringify(returnLineItem)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
							}
							// this.logger.log({ level: 'info', message: `Updating line item ${JSON.stringify(returnLineItem)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						}
					} catch (error) {
						this.logger.error(`Error creating line item: ${error}`);
						throw new Error(`Error creating line item: ${error}`);
					}
				}

				const imprints = design.imprints;

				for (const imprint of imprints) {
					const inks = imprint.inks;

					var newInks = [];
					for (const ink of inks) {
						newInks.push(ink);
					}
					this.logger.log({ level: 'info', message: `Creating imprint ${JSON.stringify(imprint.images)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
					try {
						if (imprint.id < 4) {
							delete imprint.id;
							delete imprint.imageFiles;
							const baseImprint: CreateImprintDto = { ...imprint, designId: tempID, inks: newInks };
							const returnImprint = await tx.imprint.create({
								data: {
									...baseImprint,
									images: {
										create: baseImprint.images.map((image) => ({
											url: image.url,
										})),
									},

									inks: {
										create: newInks.map((ink) => ({
											mesh: ink.mesh,
											ink: {
												connect: {
													id: ink.inkId ?? ink.id,
												},
											},
										})),
									},
								},
							});
							this.logger.log({ level: 'info', message: `Creating imprint ${JSON.stringify(returnImprint)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						} else {
							const imprintId = imprint.id;
							delete imprint.id;
							delete imprint.designId;
							delete imprint.imageFiles;
							const baseImprint: UpdateImprintDto = { ...imprint, designId: tempID, inks: newInks };

							const returnImprint = await tx.imprint.update({
								where: { id: imprintId },
								data: {
									...baseImprint,
									images: {
										deleteMany: {},
										upsert: baseImprint.images.map((image) => ({
											where: {
												id: image.id || 0,
											},
											update: {
												url: image.url,
											},
											create: {
												url: image.url,
											},
										})),
									},
									inks: {
										deleteMany: {},
										upsert: baseImprint.inks.map((ink) => ({
											where: {
												id: ink.id,
											},
											update: {
												mesh: ink.mesh,
												ink: {
													connect: {
														id: ink.inkId,
													},
												},
											},
											create: {
												mesh: ink.mesh,
												ink: {
													connect: {
														id: ink.inkId,
													},
												},
											},
										})),
									},
								},
							});
							if (currentTime - returnImprint.updatedAt.getTime() < 1000) {
								this.logger.log({ level: 'info', message: `Updating imprint ${JSON.stringify(returnImprint)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
							}
							// this.logger.log({ level: 'info', message: `Updating imprint ${JSON.stringify(returnImprint)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						}
					} catch (error) {
						this.logger.error(`Error creating imprint: ${error}`);
						throw new Error(`Error creating imprint: ${error}`);
					}
				}

				const expenses = fullBodyJob.expenses;
				for (const expense of expenses) {
					try {
						if (expense.id < 1) {
							delete expense.id;
							const baseExpense = { ...expense, jobId: returnJob.id };
							const returnExpense = await tx.expenses.create({ data: baseExpense });
							this.logger.log({ level: 'info', message: `Creating expense ${JSON.stringify(returnExpense)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						} else {
							const baseExpense = { ...expense, jobId: returnJob.id };
							const returnExpense = await tx.expenses.update({ where: { id: expense.id }, data: baseExpense });
							if (currentTime - returnExpense.updatedAt.getTime() < 1000) {
								this.logger.log({ level: 'info', message: `Updating expense ${JSON.stringify(returnExpense)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
							}
							// this.logger.log({ level: 'info', message: `Updating expense ${JSON.stringify(returnExpense)}`, refCode: '33201', type: 'job', typeId: returnJob.id });
						}
					} catch (error) {
						this.logger.error(`Error creating expense: ${error}`);
						throw new Error(`Error creating expense: ${error}`);
					}
				}
			}

			return returnJob;
		});

		return transaction;
	}
}
