/**
 * this will do a prod sync, update or add alerts to the printer, validate the next pm intervale as well as head replacement.
 *
 * if a printer is upcoming on a pm or head replacement send to the plant to address.
 *
 * a trigger on the printer table will have the ability to run this as well
 *
 * heat beats on all assigned printers
 *
 * printer status will live here this will be how we manage all the levels of status like 3 paused, 1 printing, 8 error, 10 power up, etc...
 */

import { eq } from "drizzle-orm";
import net from "net";
import { db } from "../db/db.controller.js";
import { printerData } from "../db/schema/printers.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { delay } from "../utils/delay.utils.js";
import { runProdApi } from "../utils/prodEndpoint.utils.js";
import { returnFunc } from "../utils/returnHelper.utils.js";

type Printer = {
	name: string;
	humanReadableId: string;
	type: number;
	ipAddress: string;
	port: number;
	default: boolean;
	labelInstanceIpAddress: string;
	labelInstancePort: number;
	active: boolean;
	remark: string;
	processes: number[];
};

const log = createLogger({ module: "ocp", subModule: "printers" });

export const printerManager = async () => {};

export const printerHeartBeat = async () => {
	// heat heats will be defaulted to 60 seconds no reason to allow anything else, and heart beats will only go to assigned printers no need to be monitoring non labeling printers
};

//export const printerStatus = async (statusNr: number, printerId: number) => {};
export const printerSync = async () => {
	// pull the printers from alpla prod and update them in lst

	const printers = await runProdApi({
		method: "get",
		endpoint: "/public/v1.0/Administration/Printers",
	});

	if (!printers?.success) {
		return returnFunc({
			success: false,
			level: "error",
			module: "ocp",
			subModule: "printer",
			message: printers?.message ?? "",
			data: printers?.data ?? [],
			notify: false,
		});
	}

	if (printers?.success && Array.isArray(printers.data)) {
		const ignorePrinters = ["pdf24", "standard"];

		const validPrinters =
			printers.data.filter(
				(n: any) =>
					!ignorePrinters.includes(n.name.toLowerCase()) && n.ipAddress,
			) ?? [];
		if (validPrinters.length) {
			for (const printer of validPrinters as Printer[]) {
				// run an update for each printer, do on conflicts based on the printer id
				log.debug({}, `Add/Updating ${printer.name}`);

				if (printer.active) {
					await db
						.insert(printerData)
						.values({
							name: printer.name,
							humanReadableId: printer.humanReadableId,
							ipAddress: printer.ipAddress,
							port: printer.port,
							remark: printer.remark,
							processes: printer.processes,
						})
						.onConflictDoUpdate({
							target: printerData.humanReadableId,
							set: {
								name: printer.name,
								humanReadableId: printer.humanReadableId,
								ipAddress: printer.ipAddress,
								port: printer.port,
								remark: printer.remark,
								processes: printer.processes,
							},
						})
						.returning();
					await tcpPrinter(printer);
				}

				if (!printer.active) {
					log.warn({}, `${printer.name} is not active so removing from lst.`);
					await db
						.delete(printerData)
						.where(eq(printerData.humanReadableId, printer.humanReadableId));
				}
			}
			return returnFunc({
				success: true,
				level: "info",
				module: "ocp",
				subModule: "printer",
				message: `${printers.data.length} printers were just synced, this includes new and old printers`,
				data: [],
				notify: false,
			});
		}
	}

	return returnFunc({
		success: true,
		level: "info",
		module: "ocp",
		subModule: "printer",
		message: `No printers to update`,
		data: [],
		notify: false,
	});
};

const tcpPrinter = (printer: Printer) => {
	return new Promise<void>((resolve) => {
		const socket = new net.Socket();
		const timeoutMs = 15 * 1000;

		const commands = [
			{
				key: "clearAlerts",
				command: '! U1 setvar "alerts.configured" ""\r\n',
			},
			{
				key: "addAlert",
				command: `! U1 setvar "alerts.add" "ALL MESSAGES,HTTP-POST,Y,Y,http://${process.env.SERVER_IP}:${process.env.PORT}/lst/api/ocp/printer/listener/${printer.name},0,N,printer"\r\n`,
			},
			{
				key: "setFriendlyName",
				command: `! U1 setvar "device.friendly_name" "${printer.name}"\r\n`,
			},
			{
				key: "getUniqueId",
				command: '! U1 getvar "device.unique_id"\r\n',
			},
		] as const;

		let currentCommandIndex = 0;
		let awaitingSerial = false;
		let settled = false;

		const cleanup = () => {
			socket.removeAllListeners();
			socket.destroy();
		};

		const finish = (err?: unknown) => {
			if (settled) return;
			settled = true;
			clearTimeout(timeout);
			cleanup();

			if (err) {
				log.error(
					{ err, printer: printer.name },
					`Printer update failed for ${printer.name}: doing the name and alert add directly on the printer.`,
				);
			}

			resolve();
		};

		const timeout = setTimeout(() => {
			finish(`${printer.name} timed out while updating printer config`);
		}, timeoutMs);

		const sendNext = async () => {
			if (currentCommandIndex >= commands.length) {
				socket.end();
				return;
			}

			const current = commands[currentCommandIndex];

			if (!current) {
				socket.end();
				return;
			}

			awaitingSerial = current.key === "getUniqueId";

			log.info(
				{ printer: printer.name, command: current.key },
				`Sending command to ${printer.name}`,
			);

			socket.write(current.command);

			currentCommandIndex++;

			// Small pause between commands so the printer has breathing room
			if (currentCommandIndex < commands.length) {
				await delay(1500);
				await sendNext();
			} else {
				// last command was sent, now wait for final data/close
				await delay(1500);
				socket.end();
			}
		};

		socket.connect(printer.port, printer.ipAddress, async () => {
			log.info({}, `Connected to ${printer.name}`);

			try {
				await sendNext();
			} catch (error) {
				finish(
					error instanceof Error
						? error
						: new Error(
								`Unknown error while sending commands to ${printer.name}`,
							),
				);
			}
		});

		socket.on("data", async (data) => {
			const response = data.toString().trim().replaceAll('"', "");

			log.info(
				{ printer: printer.name, response },
				`Received printer response from ${printer.name}`,
			);

			if (!awaitingSerial) return;

			awaitingSerial = false;

			try {
				await db
					.update(printerData)
					.set({ printerSN: response })
					.where(eq(printerData.humanReadableId, printer.humanReadableId));
			} catch (error) {
				finish(
					error instanceof Error
						? error
						: new Error(`Failed to update printer SN for ${printer.name}`),
				);
			}
		});

		socket.on("close", () => {
			log.info({}, `Closed connection to ${printer.name}`);
			finish();
		});

		socket.on("error", (err) => {
			finish(err);
		});
	});
};

// const tcpPrinter = async (printer: Printer) => {
// 	const p = new net.Socket();
// 	const commands = [
// 		'! U1 setvar "alerts.configured" ""\r\n', // clean install just remove all alerts
// 		`! U1 setvar "alerts.add" "ALL MESSAGES,HTTP-POST,Y,Y,http://${process.env.SERVER_IP}:${process.env.PORT}/lst/api/ocp/printer/listener/${printer.name},0,N,printer"\r\n`, // add in the all alert
// 		`! U1 setvar "device.friendly_name" "${printer.name}"\r\n`, // change the name to match the alplaprod name
// 		`! U1 getvar "device.unique_id"\r\n`, // this will get mapped into the printer as this is the one we will link to in the db.
// 		//'! U1 getvar "alerts.configured" ""\r\n',
// 	];

// 	let index = 0;
// 	const sendNext = async () => {
// 		if (index >= commands.length) {
// 			p.end();
// 			return;
// 		}

// 		const cmd = commands[index] as string;
// 		p.write(cmd);
// 		return;
// 	};

// 	p.connect(printer.port, printer.ipAddress, async () => {
// 		log.info({}, `Connected to ${printer.name}`);
// 		while (index < commands.length) {
// 			await sendNext();
// 			await delay(2000);
// 			index++;
// 		}
// 	});

// 	p.on("data", async (data) => {
// 		// this is just the sn that comes over so we will update this printer.
// 		await db
// 			.update(printerData)
// 			.set({ printerSN: data.toString().trim().replaceAll('"', "") })
// 			.where(eq(printerData.humanReadableId, printer.humanReadableId));

// 		// get the name
// 		// p.write('! U1 getvar "device.friendly_name"\r\n');
// 		// p.write('! U1 getvar "device.unique_id"\r\n');
// 		// p.write('! U1 getvar "alerts.configured"\r\n');
// 	});

// 	p.on("close", () => {
// 		log.info({}, `Closed connection to ${printer.name}`);
// 		p.destroy();
// 		return;
// 	});

// 	p.on("error", (err) => {
// 		log.info(
// 			{ stack: err },
// 			`${printer.name} encountered an error while trying to update`,
// 		);
// 		return;
// 	});
// };
