process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

import { toNodeHandler } from "better-auth/node";
import cors from "cors";
import express from "express";
import fs from "fs";
import { createServer } from "http";
import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware";
import morgan from "morgan";
import os from "os";
import { dirname, join } from "path";
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
import { fileURLToPath } from "url";
import { userMigrate } from "./src/internal/auth/controller/userMigrate.js";
import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js";
import { setupMobileRoutes } from "./src/internal/mobile/route.js";
import { printers } from "./src/internal/ocp/printers/printers.js";
import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js";
import { baseModules } from "./src/internal/system/controller/modules/baseModules.js";
import { baseSettings } from "./src/internal/system/controller/settings/baseSettings.js";
import {
	addListeners,
	manualFixes,
} from "./src/internal/system/utlis/addListeners.js";
import { swaggerOptions } from "./src/pkg/apiDocs/swaggerOptions.js";
import { auth } from "./src/pkg/auth/auth.js";
import { db } from "./src/pkg/db/db.js";
import { settings } from "./src/pkg/db/schema/settings.js";
import { createLogger } from "./src/pkg/logger/logger.js";
import { v1Listener } from "./src/pkg/logger/v1Listener.js";
import { apiHitMiddleware } from "./src/pkg/middleware/apiHits.js";
import { initializeProdPool, pool } from "./src/pkg/prodSql/prodSqlConnect.js";
import { validateEnv } from "./src/pkg/utils/envValidator.js";
import { sendNotify } from "./src/pkg/utils/notify.js";
import { returnFunc } from "./src/pkg/utils/return.js";
import { tryCatch } from "./src/pkg/utils/tryCatch.js";
import { setupIoServer } from "./src/ws/server.js";

const main = async () => {
	const env = validateEnv(process.env);
	const PORT = Number(process.env.VITE_PORT) || 4200;

	//create the logger
	const log = createLogger({ module: "system", subModule: "main start" });

	// base path
	let basePath: string = "";

	const __filename = fileURLToPath(import.meta.url);
	const __dirname = dirname(__filename);

	// Db connection stuff
	const res = await tryCatch(db.select().from(settings));

	if (res.error) {
		return returnFunc({
			success: false,
			module: "system",
			level: "fatal",
			message: `Database lookup failed`,
			notify: false,
			data: [],
		});
	}

	if (res.data.length === 0) {
		//return
		// returnFunc({
		//     success: false,
		//     module: "system",
		//     level: "fatal",
		//     message: `This seems to be the first time you have started the app please validate the settings have been intiated`,
		//     notify: false,
		//     data: [],
		// });
	}

	// connect to the prod sql
	await initializeProdPool();

	// express app
	const app = express();

	// global env that run only in dev
	if (process.env.NODE_ENV?.trim() !== "production") {
		app.use(morgan("tiny"));
		basePath = "/lst";

		app.use(
			basePath + "/test",
			express.static(join(__dirname, "../controller")),
		);
	}

	// old app prox temp stuff
	app.use(
		basePath + "/old",
		createProxyMiddleware({
			target: `http://localhost:${process.env.V1PORT || "3000"}`, // change this to pull from the correct port
			changeOrigin: true,
			pathRewrite: (path, req) => {
				// Remove the basePath + '/old' prefix from the path dynamically
				return path.replace(`${basePath}/old`, "");
			},

			headers: {
				// forward auth headers if needed
				"X-Forwarded-By": "express-proxy",
			},
		}),
	);

	// global middleware
	app.set("trust proxy", true);
	app.use(apiHitMiddleware);
	app.all(basePath + "/api/auth/*splat", toNodeHandler(auth)); // sign-in sign-out
	app.use(express.json());

	const allowedOrigins = [
		/^https?:\/\/localhost:(5173|5500|4200|3000|4000)$/, // all the allowed backend ports
		/^http?:\/\/localhost:(5173|5500|4200|3000|4000)$/,
		/^https?:\/\/.*\.alpla\.net$/,
		"http://localhost:4173",
		"http://localhost:4200",
		"http://localhost:3000",
		"http://localhost:3001",
		"http://localhost:4000",
		"http://localhost:4001",
		"http://localhost:5500",
		env.BETTER_AUTH_URL, // prod
	];

	app.use(
		cors({
			origin: (origin, callback) => {
				//console.log("CORS request from origin:", origin);

				if (!origin) return callback(null, true); // allow same-site or direct calls

				try {
					const hostname = new URL(origin).hostname; // strips protocol/port
					//console.log("Parsed hostname:", hostname);

					if (allowedOrigins.includes(origin)) {
						return callback(null, true);
					}

					// Now this works for *.alpla.net
					if (hostname.endsWith(".alpla.net") || hostname === "alpla.net") {
						return callback(null, true);
					}
				} catch (err) {
					//console.error("Invalid Origin header:", origin);
				}

				return callback(new Error("Not allowed by CORS: " + origin));
			},
			methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
			credentials: true,
			exposedHeaders: [
				"set-cookie",
				"expo-protocol-version",
				"expo-sfv-version",
			],
			allowedHeaders: [
				"Content-Type",
				"Authorization",
				"X-Requested-With",
				"XMLHttpRequest",
				"expo-runtime-version",
				"expo-platform",
				"expo-channel-name",
				"*",
			],
		}),
	);

	// docs and routes
	const openapiSpec: any = swaggerJsdoc(swaggerOptions);
	app.use(
		basePath + "/api/docs",
		swaggerUi.serve,
		swaggerUi.setup(openapiSpec),
	);

	app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build")));
	app.use(
		basePath + "/app",
		express.static(join(__dirname, "../frontend/dist")),
	);

	app.get(basePath + "/app/*splat", (req, res) => {
		res.sendFile(join(__dirname, "../frontend/dist/index.html"));
	});

	app.get(basePath + "/d/*splat", (req, res) => {
		res.sendFile(join(__dirname, "../lstDocs/build/index.html"));
	});

	//  server setup
	const server = createServer(app);

	// register app
	setupRoutes(app, basePath);

	// ws stuff
	setupIoServer(server, basePath);

	// start all systems after we are intiallally up and running
	setTimeout(() => {
		baseSettings();
		baseModules();
		printers();
		schedulerManager();

		// start up the v1listener
		v1Listener();
		addListeners();
		//userMigrate();

		// some temp fixes
		manualFixes();
	}, 5 * 1000);

	// start the server up
	server.listen(PORT, "0.0.0.0", () =>
		log.info(
			{ stack: { name: "test" } },
			`Server running in ${
				process.env.NODE_ENV ? process.env.NODE_ENV : "dev"
			}, on http://0.0.0.0:${PORT}${basePath}`,
		),
	);

	process.on("uncaughtException", async (err) => {
		//console.log("Uncaught Exception:", err);
		// await closePool();
		// const emailData = {
		//     email: "blake.matthes@alpla.com", // should be moved to the db so it can be reused.
		//     subject: `${os.hostname()} has just encountered a crash.`,
		//     template: "serverCrash",
		//     context: {
		//         error: err,
		//         plant: `${os.hostname()}`,
		//     },
		// };

		if (!process.env.WEBHOOK_URL) {
			// await sendEmail(emailData);
		} else {
			log.fatal({ stack: err.stack }, err.message);
			await sendNotify({
				module: "system",
				subModule: "fatalCrash",
				hostname: os.hostname(),
				message: err.message,
				stack: err?.stack,
			});
		}

		//process.exit(1);
	});

	process.on("SIGINT", async () => {
		console.log("\nGracefully shutting down...");
		try {
			await pool.close();
			console.log("Closed SQL connection.");
		} catch (err) {
			console.error("Error closing SQL connection:", err);
		} finally {
			process.exit(0);
		}
	});

	// Also handle other termination signals (optional)
	process.on("SIGTERM", async () => {
		console.log("SIGTERM received. Closing SQL connection...");
		try {
			await pool.close();
		} catch (err) {
			console.error(err);
		} finally {
			process.exit(0);
		}
	});

	// setInterval(() => {
	// 	const used = process.memoryUsage();
	// 	console.log(
	// 		`Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${(
	// 			used.rss / 1024 / 1024
	// 		).toFixed(2)} MB`,
	// 	);
	// }, 10000);
};

main();
