import { format, formatDuration, intervalToDuration } from "date-fns";
import { eq } from "drizzle-orm";
import { success } from "zod/v4";
import { db } from "../../../../../database/dbclient.js";
import { printerData } from "../../../../../database/schema/printers.js";
import { runProdApi } from "../../../../globalUtils/runProdApi.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { shiftChange } from "../../../sqlServer/querys/misc/shiftChange.js";
import { labelInfo } from "../../../sqlServer/querys/warehouse/labelInfo.js";

type NewLotData = {
	runningNumber: number;
	lotNumber: number;
	originalAmount: number;
	level: number;
	amount: number;
	type: "lot" | "eom";
};

interface PendingJob {
	timeoutId: NodeJS.Timeout;
	runningNumber: string | number;
	data: any;
	consumeLot: any;
	newQty: any;
	scheduledFor: Date;
}

export const pendingJobs = new Map<string | number, PendingJob>();

/**
 * Move manual material to a new lot.
 *
 * The data sent over should be
 * Running number
 * Lot number
 * Orignal Quantity
 * level of gaylord
 * amount can be sent over as a precise amount
 * type what way are we lots
 */
export const lotMaterialTransfer = async (data: NewLotData) => {
	// check if we already have this running number scheduled
	if (pendingJobs.has(data.runningNumber)) {
		const job = pendingJobs.get(data.runningNumber) as PendingJob;

		const duration = intervalToDuration({
			start: new Date(),
			end: job.scheduledFor,
		});
		createLog(
			"error",
			"materials",
			"ocp",
			`${
				data.runningNumber
			} is pending to be transfered already, remaining time ${formatDuration(
				duration,
				{ format: ["hours", "minutes", "seconds"] },
			)}`,
		);
		return {
			success: false,
			message: `${
				data.runningNumber
			} is pending to be transfered already, remaining time ${formatDuration(
				duration,
				{ format: ["hours", "minutes", "seconds"] },
			)}`,
			data: [],
		};
	}
	// get the shift time
	const { data: shift, error: shiftError } = (await tryCatch(
		query(shiftChange, "shift change from material."),
	)) as any;

	if (shiftError) {
		createLog(
			"error",
			"materials",
			"ocp",
			"There was an error getting the shift times will use fallback times",
		);
	}

	// shift split
	const shiftTimeSplit = shift?.data[0]?.shiftChange.split(":");
	console.log(parseInt(shiftTimeSplit[0]) - 1);
	// Current time
	const now = new Date();

	// Target time: today at 06:35
	const target = new Date(
		now.getFullYear(),
		now.getMonth(),
		1, //now.getDate(),
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) : 5, // this will parse the hour to remove teh zero
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 3,
		0,
		0,
	);

	console.log("target", target.toLocaleString(), "Now", now.toLocaleString());

	// to early time
	const early = new Date(
		now.getFullYear(),
		now.getMonth(),
		1, //now.getDate(),
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0,
		0,
		0,
	);

	console.log("early", early.toLocaleString(), "Now", now.toLocaleString());

	// next month just to be here
	const nextMonth = new Date(
		now.getFullYear(),
		now.getMonth() + 1,
		1, //now.getDate(),
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[0]) - 1 : 5, // this will parse the hour to remove teh zero
		shiftTimeSplit.length > 0 ? parseInt(shiftTimeSplit[1]) : 0,
		0,
		0,
	);

	console.log(
		"nextMonth",
		nextMonth.toLocaleString(),
		"Now",
		now.toLocaleString(),
	);

	// console.log(early, target);
	// if we are to early return early only if we are sending over eom
	if (data.type === "eom" && (early > now || target < now)) {
		createLog(
			"error",
			"materials",
			"ocp",
			`Eom transfers is not allowed right now please try again at ${format(
				nextMonth,
				"M/d/yyyy hh:mm",
			)} `,
		);
		return {
			success: false,
			message: `Eom transfers is not allowed right now please try again at ${format(
				nextMonth,
				"M/d/yyyy hh:mm",
			)} `,
			data: [],
		};
	}

	let timeoutTrans: number = data.type === "lot" ? 30 : 10;
	// get the barcode, and layoutID from the running number
	const { data: label, error: labelError } = (await tryCatch(
		query(
			labelInfo.replace("[runningNr]", `${data.runningNumber}`),
			"Get label info",
		),
	)) as any;

	if (labelError) {
		createLog(
			"error",
			"materials",
			"ocp",
			"There was an error getting the label info",
		);
		return {
			success: false,
			message: "There was an error getting the label info",
			data: labelError,
		};
	}

	if (label.data.length === 0) {
		createLog(
			"error",
			"materials",
			"ocp",
			`${data.runningNumber}: dose not exist or no longer in stock.`,
		);
		return {
			success: false,
			message: `${data.runningNumber}: dose not exist or no longer in stock.`,
			data: [],
		};
	}

	//console.log(label);

	if (label.data[0]?.stockStatus === "onStock") {
		createLog(
			"error",
			"materials",
			"ocp",
			`${data.runningNumber}: currently in stock and not consumed to a lot.`,
		);
		return {
			success: false,
			message: `${data.runningNumber}: currently in stock and not consumed to a lot.`,
			data: [],
		};
	}

	// get the pdf24 printer id
	const { data: printer, error: printerError } = (await tryCatch(
		db.select().from(printerData).where(eq(printerData.name, "PDF24")),
	)) as any;

	if (printerError) {
		createLog(
			"error",
			"materials",
			"ocp",
			"There was an error the printer info",
		);
		return {
			success: false,
			message: "There was an error the printer info",
			data: printerError,
		};
	}
	// calculate the remaining amount bascially it will be orignal number * level sent over
	// level should be sent in a decimal .25 .5 .75 .95 the 95 will allow basically the what looks to be a full gaylord but we always want to consume something
	const newQty =
		data.amount > 0
			? data.amount
			: (data.originalAmount * data.level).toFixed(0);

	//console.log(data.amount);

	// reprint the label and send it to pdf24
	const reprintData = {
		clientId: 999,
		runningNo: label?.data[0].runnungNumber,
		printerId: printer[0].humanReadableId,
		layoutId: label?.data[0].labelLayout,
		noOfCopies: 0,
		quantity: newQty,
	} as any;

	//console.log(reprintData);

	const { data: reprint, error: reprintError } = (await tryCatch(
		runProdApi({
			endpoint: "/public/v1.0/ProductionLabelling/ReprintLabel",
			data: [reprintData],
		}),
	)) as any;

	if (!reprint.success) {
		createLog(
			"error",
			"materials",
			"ocp",
			`RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`,
		);
		return {
			success: false,
			message: `RN:${data.runningNumber}, Reprinting Error: ${reprint.data.data.message}`,
			data: reprint,
		};
	}
	// return the label back to fm1 lane id 10001

	const matReturnData = {
		barcode: label?.data[0].Barcode,
		laneId: 10001,
	};

	//console.log(matReturnData);
	const { data: matReturn, error: matReturError } = (await tryCatch(
		runProdApi({
			endpoint:
				"/public/v1.0/IssueMaterial/ReturnPartiallyConsumedManualMaterial",
			data: [matReturnData],
		}),
	)) as any;

	if (!matReturn.success) {
		createLog(
			"error",
			"materials",
			"ocp",
			`RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`,
		);
		return {
			success: false,
			message: `RN:${data.runningNumber}, Return Error ${matReturn.data.data.errors[0].message}`,
			data: matReturn,
		};
	}
	// consume to the lot provided.
	const consumeLot = {
		productionLot: data.lotNumber,
		barcode: label?.data[0].Barcode,
	};

	const delay =
		data.type === "lot"
			? timeoutTrans * 1000
			: target.getTime() - now.getTime();

	const transfer = await transferMaterial(delay, data, consumeLot, newQty);

	if (!transfer.success) {
		return {
			success: transfer.success,
			message: transfer.message,
			data: transfer.data,
		};
	}

	const duration = intervalToDuration({ start: now, end: target });
	const pretty = formatDuration(duration, {
		format: ["hours", "minutes", "seconds"],
	});

	if (data.type === "eom") {
		return {
			success: true,
			message: `RN:${data.runningNumber}: qty: ${newQty}, will be transfered to lot: ${data.lotNumber}, in ${pretty} `,
			data: [],
		};
	} else {
		return {
			success: true,
			message: `RN:${data.runningNumber}: qty: ${newQty}, was transfered to lot: ${data.lotNumber}`,
			data: [],
		};
	}
};

const transferMaterial = async (
	delay: number,
	data: any,
	consumeLot: any,
	newQty: any,
) => {
	//console.log(data);
	if (pendingJobs.has(data.runningNumber)) {
		createLog(
			"error",
			"materials",
			"ocp",
			`${data.runningNumber} is pending to be transfered already`,
		);
		return {
			success: false,
			message: `${data.runningNumber} is pending to be transfered already`,
			data: [],
		};
	}

	const scheduledFor = new Date(Date.now() + delay);
	// sets the time out based on the type of transfer sent over.
	const timeoutId = setTimeout(async () => {
		try {
			const { data: matConsume, error: matConsumeError } = (await tryCatch(
				runProdApi({
					endpoint:
						"/public/v1.0/IssueMaterial/ConsumeNonPreparedManualMaterial",
					data: [consumeLot],
				}),
			)) as any;

			if (!matConsume?.success) {
				createLog(
					"error",
					"materials",
					"ocp",
					`RN:${data.runningNumber}, Consume Error ${
						matConsume?.data?.data?.errors?.[0]?.message ?? "Unknown"
					}`,
				);
				return; // still hits finally
			}

			createLog(
				"info",
				"materials",
				"ocp",
				`RN:${data.runningNumber}: qty: ${newQty}, was transferred to lot:${data.lotNumber}`,
			);
		} catch (err) {
			createLog(
				"error",
				"materials",
				"ocp",
				`RN:${data.runningNumber}, ${err}`,
			);
		} finally {
			// Always clear the pending entry, even if error
			pendingJobs.delete(data.runningNumber);
		}
	}, delay);

	pendingJobs.set(data.runningNumber, {
		timeoutId,
		runningNumber: data.runningNumber,
		data,
		consumeLot,
		newQty,
		scheduledFor,
	});

	// Immediately say we scheduled it
	return {
		success: true,
		message: `Transfer for ${data.runningNumber} scheduled`,
		data: [],
	};
};

// setInterval(() => {
//     console.log(pendingJobs);
// }, 5000);

// setTimeout(async () => {
//     lotMaterialTransfer({
//         runnungNumber: 603468,
//         lotNumber: 24897,
//         originalAmount: 380,
//         level: 0.95,
//     });
// }, 5000);
