require("dotenv").config();

// Validate required environment variables on startup
function validateEnvironmentVariables() {
	const requiredVars = {
		JWT_SECRET: "Required for secure authentication token generation",
		DATABASE_URL: "Required for database connection",
	};

	const missing = [];

	// Check required variables
	for (const [varName, description] of Object.entries(requiredVars)) {
		if (!process.env[varName]) {
			missing.push(`${varName}: ${description}`);
		}
	}

	// Fail if required variables are missing
	if (missing.length > 0) {
		console.error("❌ Missing required environment variables:");
		for (const error of missing) {
			console.error(`   - ${error}`);
		}
		console.error("");
		console.error(
			"Please set these environment variables and restart the application.",
		);
		process.exit(1);
	}

	console.log("✅ Environment variable validation passed");
}

// Validate environment variables before importing any modules that depend on them
validateEnvironmentVariables();

const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const cookieParser = require("cookie-parser");
const {
	getPrismaClient,
	waitForDatabase,
	disconnectPrisma,
} = require("./config/prisma");
const winston = require("winston");

// Import routes
const authRoutes = require("./routes/authRoutes");
const hostRoutes = require("./routes/hostRoutes");
const hostGroupRoutes = require("./routes/hostGroupRoutes");
const packageRoutes = require("./routes/packageRoutes");
const dashboardRoutes = require("./routes/dashboardRoutes");
const permissionsRoutes = require("./routes/permissionsRoutes");
const settingsRoutes = require("./routes/settingsRoutes");
const {
	router: dashboardPreferencesRoutes,
} = require("./routes/dashboardPreferencesRoutes");
const repositoryRoutes = require("./routes/repositoryRoutes");
const versionRoutes = require("./routes/versionRoutes");
const tfaRoutes = require("./routes/tfaRoutes");
const searchRoutes = require("./routes/searchRoutes");
const autoEnrollmentRoutes = require("./routes/autoEnrollmentRoutes");
const gethomepageRoutes = require("./routes/gethomepageRoutes");
const automationRoutes = require("./routes/automationRoutes");
const dockerRoutes = require("./routes/dockerRoutes");
const integrationRoutes = require("./routes/integrationRoutes");
const wsRoutes = require("./routes/wsRoutes");
const agentVersionRoutes = require("./routes/agentVersionRoutes");
const metricsRoutes = require("./routes/metricsRoutes");
const { initSettings } = require("./services/settingsService");
const { queueManager } = require("./services/automation");
const { authenticateToken, requireAdmin } = require("./middleware/auth");
const { createBullBoard } = require("@bull-board/api");
const { BullMQAdapter } = require("@bull-board/api/bullMQAdapter");
const { ExpressAdapter } = require("@bull-board/express");

// Initialize Prisma client with optimized connection pooling for multiple instances
const prisma = getPrismaClient();

// Function to check and create default role permissions on startup
async function checkAndCreateRolePermissions() {
	console.log("🔐 Starting role permissions auto-creation check...");

	// Skip if auto-creation is disabled
	if (process.env.AUTO_CREATE_ROLE_PERMISSIONS === "false") {
		console.log("❌ Auto-creation of role permissions is disabled");
		if (process.env.ENABLE_LOGGING === "true") {
			logger.info("Auto-creation of role permissions is disabled");
		}
		return;
	}

	try {
		const crypto = require("node:crypto");

		// Define default roles and permissions
		const defaultRoles = [
			{
				id: crypto.randomUUID(),
				role: "admin",
				can_view_dashboard: true,
				can_view_hosts: true,
				can_manage_hosts: true,
				can_view_packages: true,
				can_manage_packages: true,
				can_view_users: true,
				can_manage_users: true,
				can_view_reports: true,
				can_export_data: true,
				can_manage_settings: true,
				created_at: new Date(),
				updated_at: new Date(),
			},
			{
				id: crypto.randomUUID(),
				role: "user",
				can_view_dashboard: true,
				can_view_hosts: true,
				can_manage_hosts: false,
				can_view_packages: true,
				can_manage_packages: false,
				can_view_users: false,
				can_manage_users: false,
				can_view_reports: true,
				can_export_data: false,
				can_manage_settings: false,
				created_at: new Date(),
				updated_at: new Date(),
			},
		];

		const createdRoles = [];
		const existingRoles = [];

		for (const roleData of defaultRoles) {
			// Check if role already exists
			const existingRole = await prisma.role_permissions.findUnique({
				where: { role: roleData.role },
			});

			if (existingRole) {
				console.log(`✅ Role '${roleData.role}' already exists in database`);
				existingRoles.push(existingRole);
				if (process.env.ENABLE_LOGGING === "true") {
					logger.info(`Role '${roleData.role}' already exists in database`);
				}
			} else {
				// Create new role permission
				const permission = await prisma.role_permissions.create({
					data: roleData,
				});
				createdRoles.push(permission);
				console.log(`🆕 Created role '${roleData.role}' with permissions`);
				if (process.env.ENABLE_LOGGING === "true") {
					logger.info(`Created role '${roleData.role}' with permissions`);
				}
			}
		}

		if (createdRoles.length > 0) {
			console.log(
				`🎉 Successfully auto-created ${createdRoles.length} role permissions on startup`,
			);
			console.log("📋 Created roles:");
			createdRoles.forEach((role) => {
				console.log(
					`   • ${role.role}: dashboard=${role.can_view_dashboard}, hosts=${role.can_manage_hosts}, packages=${role.can_manage_packages}, users=${role.can_manage_users}, settings=${role.can_manage_settings}`,
				);
			});

			if (process.env.ENABLE_LOGGING === "true") {
				logger.info(
					`✅ Auto-created ${createdRoles.length} role permissions on startup`,
				);
			}
		} else {
			console.log(
				`✅ All default role permissions already exist (${existingRoles.length} roles verified)`,
			);
			if (process.env.ENABLE_LOGGING === "true") {
				logger.info(
					`All default role permissions already exist (${existingRoles.length} roles verified)`,
				);
			}
		}
	} catch (error) {
		console.error(
			"❌ Failed to check/create role permissions on startup:",
			error.message,
		);
		if (process.env.ENABLE_LOGGING === "true") {
			logger.error(
				"Failed to check/create role permissions on startup:",
				error.message,
			);
		}
	}
}

// Initialize logger - only if logging is enabled
const logger =
	process.env.ENABLE_LOGGING === "true"
		? winston.createLogger({
				level: process.env.LOG_LEVEL || "info",
				format: winston.format.combine(
					winston.format.timestamp(),
					winston.format.errors({ stack: true }),
					winston.format.json(),
				),
				transports: [],
			})
		: {
				info: () => {},
				error: () => {},
				warn: () => {},
				debug: () => {},
			};

// Configure transports based on PM_LOG_TO_CONSOLE environment variable
if (process.env.ENABLE_LOGGING === "true") {
	const logToConsole =
		process.env.PM_LOG_TO_CONSOLE === "1" ||
		process.env.PM_LOG_TO_CONSOLE === "true";

	if (logToConsole) {
		// Log to stdout/stderr instead of files
		logger.add(
			new winston.transports.Console({
				format: winston.format.combine(
					winston.format.timestamp(),
					winston.format.errors({ stack: true }),
					winston.format.printf(({ timestamp, level, message, stack }) => {
						return `${timestamp} [${level.toUpperCase()}]: ${stack || message}`;
					}),
				),
				stderrLevels: ["error", "warn"],
			}),
		);
	} else {
		// Log to files (default behavior)
		logger.add(
			new winston.transports.File({
				filename: "logs/error.log",
				level: "error",
			}),
		);
		logger.add(new winston.transports.File({ filename: "logs/combined.log" }));

		// Also add console logging for non-production environments
		if (process.env.NODE_ENV !== "production") {
			logger.add(
				new winston.transports.Console({
					format: winston.format.simple(),
				}),
			);
		}
	}
}

const app = express();
const PORT = process.env.PORT || 3001;
const http = require("node:http");
const server = http.createServer(app);
const { init: initAgentWs } = require("./services/agentWs");
const agentVersionService = require("./services/agentVersionService");

// Trust proxy (needed when behind reverse proxy) and remove X-Powered-By
if (process.env.TRUST_PROXY) {
	const trustProxyValue = process.env.TRUST_PROXY;

	// Parse the trust proxy setting according to Express documentation
	if (trustProxyValue === "true") {
		app.set("trust proxy", true);
	} else if (trustProxyValue === "false") {
		app.set("trust proxy", false);
	} else if (/^\d+$/.test(trustProxyValue)) {
		// If it's a number (hop count)
		app.set("trust proxy", parseInt(trustProxyValue, 10));
	} else {
		// If it contains commas, split into array; otherwise use as single value
		// This handles: IP addresses, subnets, named subnets (loopback, linklocal, uniquelocal)
		app.set(
			"trust proxy",
			trustProxyValue.includes(",")
				? trustProxyValue.split(",").map((s) => s.trim())
				: trustProxyValue,
		);
	}
} else {
	app.set("trust proxy", 1);
}
app.disable("x-powered-by");

// Rate limiting with monitoring
const limiter = rateLimit({
	windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 15 * 60 * 1000,
	max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 5000,
	message: {
		error: "Too many requests from this IP, please try again later.",
		retryAfter: Math.ceil(
			(parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 15 * 60 * 1000) / 1000,
		),
	},
	standardHeaders: true,
	legacyHeaders: false,
	skipSuccessfulRequests: true, // Don't count successful requests
	skipFailedRequests: false, // Count failed requests
});

// Middleware
// Helmet with stricter defaults (CSP/HSTS only in production)
app.use(
	helmet({
		contentSecurityPolicy:
			process.env.NODE_ENV === "production"
				? {
						useDefaults: true,
						directives: {
							defaultSrc: ["'self'"],
							scriptSrc: ["'self'"],
							styleSrc: ["'self'", "'unsafe-inline'"],
							imgSrc: ["'self'", "data:"],
							fontSrc: ["'self'", "data:"],
							connectSrc: ["'self'"],
							frameAncestors: ["'none'"],
							objectSrc: ["'none'"],
						},
					}
				: false,
		hsts:
			process.env.ENABLE_HSTS === "true" ||
			process.env.NODE_ENV === "production",
	}),
);

// CORS allowlist from comma-separated env
const parseOrigins = (val) =>
	(val || "")
		.split(",")
		.map((s) => s.trim())
		.filter(Boolean);
const allowedOrigins = parseOrigins(
	process.env.CORS_ORIGINS ||
		process.env.CORS_ORIGIN ||
		"http://localhost:3000",
);

// Add Bull Board origin to allowed origins if not already present
const bullBoardOrigin = process.env.CORS_ORIGIN || "http://localhost:3000";
if (!allowedOrigins.includes(bullBoardOrigin)) {
	allowedOrigins.push(bullBoardOrigin);
}

app.use(
	cors({
		origin: (origin, callback) => {
			// Allow non-browser/SSR tools with no origin
			if (!origin) return callback(null, true);
			if (allowedOrigins.includes(origin)) return callback(null, true);

			// Allow Bull Board requests from the same origin as CORS_ORIGIN
			if (origin === bullBoardOrigin) return callback(null, true);

			// Allow same-origin requests (e.g., Bull Board accessing its own API)
			// This allows http://hostname:3001 to make requests to http://hostname:3001
			if (origin?.includes(":3001")) return callback(null, true);

			// Allow Bull Board requests from the frontend origin (same host, different port)
			// This handles cases where frontend is on port 3000 and backend on 3001
			const frontendOrigin = origin?.replace(/:3001$/, ":3000");
			if (frontendOrigin && allowedOrigins.includes(frontendOrigin)) {
				return callback(null, true);
			}

			return callback(new Error("Not allowed by CORS"));
		},
		credentials: true,
		// Additional CORS options for better cookie handling
		optionsSuccessStatus: 200,
		methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
		allowedHeaders: [
			"Content-Type",
			"Authorization",
			"Cookie",
			"X-Requested-With",
		],
	}),
);
app.use(limiter);
// Cookie parser for Bull Board sessions
app.use(cookieParser());
// Reduce body size limits to reasonable defaults
app.use(express.json({ limit: process.env.JSON_BODY_LIMIT || "5mb" }));
app.use(
	express.urlencoded({
		extended: true,
		limit: process.env.JSON_BODY_LIMIT || "5mb",
	}),
);

// Request logging - only if logging is enabled
if (process.env.ENABLE_LOGGING === "true") {
	app.use((req, _, next) => {
		// Log health check requests at debug level to reduce log spam
		if (req.path === "/health") {
			logger.debug(`${req.method} ${req.path} - ${req.ip}`);
		} else {
			logger.info(`${req.method} ${req.path} - ${req.ip}`);
		}
		next();
	});
}

// Health check endpoint
app.get("/health", (_req, res) => {
	res.json({ status: "ok", timestamp: new Date().toISOString() });
});

// API routes
const apiVersion = process.env.API_VERSION || "v1";

// Per-route rate limits with monitoring
const authLimiter = rateLimit({
	windowMs:
		parseInt(process.env.AUTH_RATE_LIMIT_WINDOW_MS, 10) || 10 * 60 * 1000,
	max: parseInt(process.env.AUTH_RATE_LIMIT_MAX, 10) || 500,
	message: {
		error: "Too many authentication requests, please try again later.",
		retryAfter: Math.ceil(
			(parseInt(process.env.AUTH_RATE_LIMIT_WINDOW_MS, 10) || 10 * 60 * 1000) /
				1000,
		),
	},
	standardHeaders: true,
	legacyHeaders: false,
	skipSuccessfulRequests: true,
});
const agentLimiter = rateLimit({
	windowMs: parseInt(process.env.AGENT_RATE_LIMIT_WINDOW_MS, 10) || 60 * 1000,
	max: parseInt(process.env.AGENT_RATE_LIMIT_MAX, 10) || 1000,
	message: {
		error: "Too many agent requests, please try again later.",
		retryAfter: Math.ceil(
			(parseInt(process.env.AGENT_RATE_LIMIT_WINDOW_MS, 10) || 60 * 1000) /
				1000,
		),
	},
	standardHeaders: true,
	legacyHeaders: false,
	skipSuccessfulRequests: true,
});

app.use(`/api/${apiVersion}/auth`, authLimiter, authRoutes);
app.use(`/api/${apiVersion}/hosts`, agentLimiter, hostRoutes);
app.use(`/api/${apiVersion}/host-groups`, hostGroupRoutes);
app.use(`/api/${apiVersion}/packages`, packageRoutes);
app.use(`/api/${apiVersion}/dashboard`, dashboardRoutes);
app.use(`/api/${apiVersion}/permissions`, permissionsRoutes);
app.use(`/api/${apiVersion}/settings`, settingsRoutes);
app.use(`/api/${apiVersion}/dashboard-preferences`, dashboardPreferencesRoutes);
app.use(`/api/${apiVersion}/repositories`, repositoryRoutes);
app.use(`/api/${apiVersion}/version`, versionRoutes);
app.use(`/api/${apiVersion}/tfa`, tfaRoutes);
app.use(`/api/${apiVersion}/search`, searchRoutes);
app.use(
	`/api/${apiVersion}/auto-enrollment`,
	authLimiter,
	autoEnrollmentRoutes,
);
app.use(`/api/${apiVersion}/gethomepage`, gethomepageRoutes);
app.use(`/api/${apiVersion}/automation`, automationRoutes);
app.use(`/api/${apiVersion}/docker`, dockerRoutes);
app.use(`/api/${apiVersion}/integrations`, integrationRoutes);
app.use(`/api/${apiVersion}/ws`, wsRoutes);
app.use(`/api/${apiVersion}/agent`, agentVersionRoutes);
app.use(`/api/${apiVersion}/metrics`, metricsRoutes);

// Bull Board - will be populated after queue manager initializes
let bullBoardRouter = null;
const _bullBoardSessions = new Map(); // Store authenticated sessions

// Mount Bull Board at /bullboard for cleaner URL
app.use(`/bullboard`, (_req, res, next) => {
	// Relax COOP/COEP for Bull Board in non-production to avoid browser warnings
	if (process.env.NODE_ENV !== "production") {
		res.setHeader("Cross-Origin-Opener-Policy", "same-origin-allow-popups");
		res.setHeader("Cross-Origin-Embedder-Policy", "unsafe-none");
	}

	// Add headers to help with WebSocket connections
	res.setHeader("X-Frame-Options", "SAMEORIGIN");
	res.setHeader(
		"Content-Security-Policy",
		"default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws: wss:;",
	);

	next();
});

// Simplified Bull Board authentication - just validate token once and set a simple auth cookie
app.use(`/bullboard`, async (req, res, next) => {
	// Skip authentication for static assets
	if (req.path.includes("/static/") || req.path.includes("/favicon")) {
		return next();
	}

	// Check for existing Bull Board auth cookie
	if (req.cookies["bull-board-auth"]) {
		// Already authenticated, allow access
		return next();
	}

	// No auth cookie - check for token in query
	const token = req.query.token;
	if (!token) {
		return res.status(401).json({
			error:
				"Authentication required. Please access Bull Board from the Automation page.",
		});
	}

	// Validate token and set auth cookie
	req.headers.authorization = `Bearer ${token}`;
	return authenticateToken(req, res, (err) => {
		if (err) {
			return res.status(401).json({ error: "Invalid authentication token" });
		}
		return requireAdmin(req, res, (adminErr) => {
			if (adminErr) {
				return res.status(403).json({ error: "Admin access required" });
			}

			// Set a simple auth cookie that will persist for the session
			res.cookie("bull-board-auth", token, {
				httpOnly: false,
				secure: false,
				maxAge: 3600000, // 1 hour
				path: "/bullboard",
				sameSite: "lax",
			});

			console.log("Bull Board - Authentication successful, cookie set");
			return next();
		});
	});
});

// Remove all the old complex middleware below and replace with the new Bull Board router setup
app.use(`/bullboard`, (req, res, next) => {
	if (bullBoardRouter) {
		return bullBoardRouter(req, res, next);
	}
	return res.status(503).json({ error: "Bull Board not initialized yet" });
});

/*
// OLD MIDDLEWARE - REMOVED FOR SIMPLIFICATION - DO NOT USE
if (false) {
		const sessionId = req.cookies["bull-board-session"];
		console.log("Bull Board API call - Session ID:", sessionId ? "present" : "missing");
		console.log("Bull Board API call - Cookies:", req.cookies);
		console.log("Bull Board API call - Bull Board token cookie:", req.cookies["bull-board-token"] ? "present" : "missing");
		console.log("Bull Board API call - Query token:", req.query.token ? "present" : "missing");
		console.log("Bull Board API call - Auth header:", req.headers.authorization ? "present" : "missing");
		console.log("Bull Board API call - Origin:", req.headers.origin || "missing");
		console.log("Bull Board API call - Referer:", req.headers.referer || "missing");
		
		// Check if we have any authentication method available
		const hasSession = !!sessionId;
		const hasTokenCookie = !!req.cookies["bull-board-token"];
		const hasQueryToken = !!req.query.token;
		const hasAuthHeader = !!req.headers.authorization;
		const hasReferer = !!req.headers.referer;
		
		console.log("Bull Board API call - Auth methods available:", {
			session: hasSession,
			tokenCookie: hasTokenCookie,
			queryToken: hasQueryToken,
			authHeader: hasAuthHeader,
			referer: hasReferer
		});
		
		// Check for valid session first
		if (sessionId) {
			const session = bullBoardSessions.get(sessionId);
			console.log("Bull Board API call - Session found:", !!session);
			if (session && Date.now() - session.timestamp < 3600000) {
				// Valid session, extend it
				session.timestamp = Date.now();
				console.log("Bull Board API call - Using existing session, proceeding");
				return next();
			} else if (session) {
				// Expired session, remove it
				console.log("Bull Board API call - Session expired, removing");
				bullBoardSessions.delete(sessionId);
			}
		}
		
		// No valid session, check for token as fallback
		let token = req.query.token;
		if (!token && req.headers.authorization) {
			token = req.headers.authorization.replace("Bearer ", "");
		}
		if (!token && req.cookies["bull-board-token"]) {
			token = req.cookies["bull-board-token"];
		}
		
		// For API calls, also check if the token is in the referer URL
		// This handles cases where the main page hasn't set the cookie yet
		if (!token && req.headers.referer) {
			try {
				const refererUrl = new URL(req.headers.referer);
				const refererToken = refererUrl.searchParams.get('token');
				if (refererToken) {
					token = refererToken;
					console.log("Bull Board API call - Token found in referer URL:", refererToken.substring(0, 20) + "...");
				} else {
					console.log("Bull Board API call - No token found in referer URL");
					// If no token in referer and no session, return 401 with redirect info
					if (!sessionId) {
						console.log("Bull Board API call - No authentication available, returning 401");
						return res.status(401).json({ 
							error: "Authentication required", 
							message: "Please refresh the page to re-authenticate"
						});
					}
				}
			} catch (error) {
				console.log("Bull Board API call - Error parsing referer URL:", error.message);
			}
		}
		
		if (token) {
			console.log("Bull Board API call - Token found, authenticating");
			// Add token to headers for authentication
			req.headers.authorization = `Bearer ${token}`;
			
			// Authenticate the user
			return authenticateToken(req, res, (err) => {
				if (err) {
					console.log("Bull Board API call - Token authentication failed");
					return res.status(401).json({ error: "Authentication failed" });
				}
				return requireAdmin(req, res, (adminErr) => {
					if (adminErr) {
						console.log("Bull Board API call - Admin access required");
						return res.status(403).json({ error: "Admin access required" });
					}
					console.log("Bull Board API call - Token authentication successful");
					return next();
				});
			});
		}
		
		// No valid session or token for API calls, deny access
		console.log("Bull Board API call - No valid session or token, denying access");
		return res.status(401).json({ error: "Valid Bull Board session or token required" });
	}

	// Check for bull-board-session cookie first
	const sessionId = req.cookies["bull-board-session"];
	if (sessionId) {
		const session = bullBoardSessions.get(sessionId);
		if (session && Date.now() - session.timestamp < 3600000) {
			// 1 hour
			// Valid session, extend it
			session.timestamp = Date.now();
			return next();
		} else if (session) {
			// Expired session, remove it
			bullBoardSessions.delete(sessionId);
		}
	}

	// No valid session, check for token
	let token = req.query.token;
	if (!token && req.headers.authorization) {
		token = req.headers.authorization.replace("Bearer ", "");
	}
	if (!token && req.cookies["bull-board-token"]) {
		token = req.cookies["bull-board-token"];
	}

	// If no token, deny access
	if (!token) {
		return res.status(401).json({ error: "Access token required" });
	}

	// Add token to headers for authentication
	req.headers.authorization = `Bearer ${token}`;

	// Authenticate the user
	return authenticateToken(req, res, (err) => {
		if (err) {
			return res.status(401).json({ error: "Authentication failed" });
		}
		return requireAdmin(req, res, (adminErr) => {
			if (adminErr) {
				return res.status(403).json({ error: "Admin access required" });
			}

			// Authentication successful - create a session
			const newSessionId = require("node:crypto")
				.randomBytes(32)
				.toString("hex");
			bullBoardSessions.set(newSessionId, {
				timestamp: Date.now(),
				userId: req.user.id,
			});

			// Set session cookie with proper configuration for domain access
			const isHttps = process.env.NODE_ENV === "production" || process.env.SERVER_PROTOCOL === "https";
			const cookieOptions = {
				httpOnly: true,
				secure: isHttps,
				maxAge: 3600000, // 1 hour
				path: "/", // Set path to root so it's available for all Bull Board requests
			};
			
			// Configure sameSite based on protocol and environment
			if (isHttps) {
				cookieOptions.sameSite = "none"; // Required for HTTPS cross-origin
			} else {
				cookieOptions.sameSite = "lax"; // Better for HTTP same-origin
			}
			
			res.cookie("bull-board-session", newSessionId, cookieOptions);

			// Clean up old sessions periodically
			if (bullBoardSessions.size > 100) {
				const now = Date.now();
				for (const [sid, session] of bullBoardSessions.entries()) {
					if (now - session.timestamp > 3600000) {
						bullBoardSessions.delete(sid);
					}
				}
			}

			return next();
		});
	});
});
*/

// Second middleware block - COMMENTED OUT - using simplified version above instead
/*
app.use(`/bullboard`, (req, res, next) => {
	if (bullBoardRouter) {
		// If this is the main Bull Board page (not an API call), inject the token and create session
		if (!req.path.includes("/api/") && !req.path.includes("/static/") && req.path === "/bullboard") {
			const token = req.query.token;
			console.log("Bull Board main page - Token:", token ? "present" : "missing");
			console.log("Bull Board main page - Query params:", req.query);
			console.log("Bull Board main page - Origin:", req.headers.origin || "missing");
			console.log("Bull Board main page - Referer:", req.headers.referer || "missing");
			console.log("Bull Board main page - Cookies:", req.cookies);
			
			if (token) {
				// Authenticate the user and create a session immediately on page load
				req.headers.authorization = `Bearer ${token}`;
				
				return authenticateToken(req, res, (err) => {
					if (err) {
						console.log("Bull Board main page - Token authentication failed");
						return res.status(401).json({ error: "Authentication failed" });
					}
					return requireAdmin(req, res, (adminErr) => {
						if (adminErr) {
							console.log("Bull Board main page - Admin access required");
							return res.status(403).json({ error: "Admin access required" });
						}
						
						console.log("Bull Board main page - Token authentication successful, creating session");
						
						// Create a Bull Board session immediately
						const newSessionId = require("node:crypto")
							.randomBytes(32)
							.toString("hex");
						bullBoardSessions.set(newSessionId, {
							timestamp: Date.now(),
							userId: req.user.id,
						});

						// Set session cookie with proper configuration for domain access
						const sessionCookieOptions = {
							httpOnly: true,
							secure: false, // Always false for HTTP
							maxAge: 3600000, // 1 hour
							path: "/", // Set path to root so it's available for all Bull Board requests
							sameSite: "lax", // Always lax for HTTP
						};
						
						res.cookie("bull-board-session", newSessionId, sessionCookieOptions);
						console.log("Bull Board main page - Session created:", newSessionId);
						console.log("Bull Board main page - Cookie options:", sessionCookieOptions);
						
						// Also set a token cookie for API calls as a fallback
						const tokenCookieOptions = {
							httpOnly: false, // Allow JavaScript to access it
							secure: false, // Always false for HTTP
							maxAge: 3600000, // 1 hour
							path: "/", // Set path to root for broader compatibility
							sameSite: "lax", // Always lax for HTTP
						};
						
						res.cookie("bull-board-token", token, tokenCookieOptions);
						console.log("Bull Board main page - Token cookie also set for API fallback");
						
						// Clean up old sessions periodically
						if (bullBoardSessions.size > 100) {
							const now = Date.now();
							for (const [sid, session] of bullBoardSessions.entries()) {
								if (now - session.timestamp > 3600000) {
									bullBoardSessions.delete(sid);
								}
							}
						}
						
						// Now proceed to serve the Bull Board page
						return bullBoardRouter(req, res, next);
					});
				});
			} else {
				console.log("Bull Board main page - No token provided, checking for existing session");
				// Check if we have an existing session
				const sessionId = req.cookies["bull-board-session"];
				if (sessionId) {
					const session = bullBoardSessions.get(sessionId);
					if (session && Date.now() - session.timestamp < 3600000) {
						console.log("Bull Board main page - Using existing session");
						// Extend session
						session.timestamp = Date.now();
						return bullBoardRouter(req, res, next);
					} else if (session) {
						console.log("Bull Board main page - Session expired, removing");
						bullBoardSessions.delete(sessionId);
					}
				}
				console.log("Bull Board main page - No valid session, denying access");
				return res.status(401).json({ error: "Access token required" });
			}
		}
		return bullBoardRouter(req, res, next);
	}
	return res.status(503).json({ error: "Bull Board not initialized yet" });
});
*/

// Error handler specifically for Bull Board routes
app.use("/bullboard", (err, req, res, _next) => {
	console.error("Bull Board error on", req.method, req.url);
	console.error("Error details:", err.message);
	console.error("Stack:", err.stack);
	if (process.env.ENABLE_LOGGING === "true") {
		logger.error(`Bull Board error on ${req.method} ${req.url}:`, err);
	}
	res.status(500).json({
		error: "Internal server error",
		message: err.message,
		path: req.path,
		url: req.url,
	});
});

// Error handling middleware
app.use((err, _req, res, _next) => {
	if (process.env.ENABLE_LOGGING === "true") {
		logger.error(err.stack);
	}

	// Special handling for CORS errors - always include the message
	if (err.message?.includes("Not allowed by CORS")) {
		return res.status(500).json({
			error: "Something went wrong!",
			message: err.message, // Always include CORS error message
		});
	}

	res.status(500).json({
		error: "Something went wrong!",
		message: process.env.NODE_ENV === "development" ? err.message : undefined,
	});
});

// 404 handler
app.use("*", (_req, res) => {
	res.status(404).json({ error: "Route not found" });
});

// Graceful shutdown
process.on("SIGINT", async () => {
	if (process.env.ENABLE_LOGGING === "true") {
		logger.info("SIGINT received, shutting down gracefully");
	}
	await queueManager.shutdown();
	await disconnectPrisma(prisma);
	process.exit(0);
});

process.on("SIGTERM", async () => {
	if (process.env.ENABLE_LOGGING === "true") {
		logger.info("SIGTERM received, shutting down gracefully");
	}
	await queueManager.shutdown();
	await disconnectPrisma(prisma);
	process.exit(0);
});

// Initialize dashboard preferences for all users
async function initializeDashboardPreferences() {
	try {
		// Get all users
		const users = await prisma.users.findMany({
			select: {
				id: true,
				username: true,
				email: true,
				role: true,
				dashboard_preferences: {
					select: {
						card_id: true,
					},
				},
			},
		});

		if (users.length === 0) {
			return;
		}

		let initializedCount = 0;
		let updatedCount = 0;

		for (const user of users) {
			const hasPreferences = user.dashboard_preferences.length > 0;

			// Get permission-based preferences for this user's role
			const expectedPreferences = await getPermissionBasedPreferences(
				user.role,
			);
			const expectedCardCount = expectedPreferences.length;

			if (!hasPreferences) {
				// User has no preferences - create them
				const preferencesData = expectedPreferences.map((pref) => ({
					id: require("uuid").v4(),
					user_id: user.id,
					card_id: pref.cardId,
					enabled: pref.enabled,
					order: pref.order,
					created_at: new Date(),
					updated_at: new Date(),
				}));

				await prisma.dashboard_preferences.createMany({
					data: preferencesData,
				});

				initializedCount++;
			} else {
				// User already has preferences - check if they need updating
				const currentCardCount = user.dashboard_preferences.length;

				if (currentCardCount !== expectedCardCount) {
					// Delete existing preferences
					await prisma.dashboard_preferences.deleteMany({
						where: { user_id: user.id },
					});

					// Create new preferences based on permissions
					const preferencesData = expectedPreferences.map((pref) => ({
						id: require("uuid").v4(),
						user_id: user.id,
						card_id: pref.cardId,
						enabled: pref.enabled,
						order: pref.order,
						created_at: new Date(),
						updated_at: new Date(),
					}));

					await prisma.dashboard_preferences.createMany({
						data: preferencesData,
					});

					updatedCount++;
				}
			}
		}

		// Only show summary if there were changes
		if (initializedCount > 0 || updatedCount > 0) {
			console.log(
				`✅ Dashboard preferences: ${initializedCount} initialized, ${updatedCount} updated`,
			);
		}
	} catch (error) {
		console.error("❌ Error initializing dashboard preferences:", error);
		throw error;
	}
}

// Helper function to get user permissions based on role
async function getUserPermissions(userRole) {
	try {
		const permissions = await prisma.role_permissions.findUnique({
			where: { role: userRole },
		});

		// If no specific permissions found, return default admin permissions (for backward compatibility)
		if (!permissions) {
			console.warn(
				`No permissions found for role: ${userRole}, defaulting to admin access`,
			);
			return {
				can_view_dashboard: true,
				can_view_hosts: true,
				can_manage_hosts: true,
				can_view_packages: true,
				can_manage_packages: true,
				can_view_users: true,
				can_manage_users: true,
				can_view_reports: true,
				can_export_data: true,
				can_manage_settings: true,
			};
		}

		return permissions;
	} catch (error) {
		console.error("Error fetching user permissions:", error);
		// Return admin permissions as fallback
		return {
			can_view_dashboard: true,
			can_view_hosts: true,
			can_manage_hosts: true,
			can_view_packages: true,
			can_manage_packages: true,
			can_view_users: true,
			can_manage_users: true,
			can_view_reports: true,
			can_export_data: true,
			can_manage_settings: true,
		};
	}
}

// Helper function to get permission-based dashboard preferences for a role
async function getPermissionBasedPreferences(userRole) {
	// Get user's actual permissions
	const permissions = await getUserPermissions(userRole);

	// Define all possible dashboard cards with their required permissions
	const allCards = [
		// Host-related cards
		{ cardId: "totalHosts", requiredPermission: "can_view_hosts", order: 0 },
		{
			cardId: "hostsNeedingUpdates",
			requiredPermission: "can_view_hosts",
			order: 1,
		},

		// Package-related cards
		{
			cardId: "totalOutdatedPackages",
			requiredPermission: "can_view_packages",
			order: 2,
		},
		{
			cardId: "securityUpdates",
			requiredPermission: "can_view_packages",
			order: 3,
		},

		// Host-related cards (continued)
		{
			cardId: "totalHostGroups",
			requiredPermission: "can_view_hosts",
			order: 4,
		},
		{ cardId: "upToDateHosts", requiredPermission: "can_view_hosts", order: 5 },

		// Repository-related cards
		{ cardId: "totalRepos", requiredPermission: "can_view_hosts", order: 6 }, // Repos are host-related

		// User management cards (admin only)
		{ cardId: "totalUsers", requiredPermission: "can_view_users", order: 7 },

		// System/Report cards
		{
			cardId: "osDistribution",
			requiredPermission: "can_view_reports",
			order: 8,
		},
		{
			cardId: "osDistributionBar",
			requiredPermission: "can_view_reports",
			order: 9,
		},
		{
			cardId: "osDistributionDoughnut",
			requiredPermission: "can_view_reports",
			order: 10,
		},
		{
			cardId: "recentCollection",
			requiredPermission: "can_view_hosts",
			order: 11,
		}, // Collection is host-related
		{
			cardId: "updateStatus",
			requiredPermission: "can_view_reports",
			order: 12,
		},
		{
			cardId: "packagePriority",
			requiredPermission: "can_view_packages",
			order: 13,
		},
		{
			cardId: "packageTrends",
			requiredPermission: "can_view_packages",
			order: 14,
		},
		{ cardId: "recentUsers", requiredPermission: "can_view_users", order: 15 },
		{
			cardId: "quickStats",
			requiredPermission: "can_view_dashboard",
			order: 16,
		},
	];

	// Filter cards based on user's permissions
	const allowedCards = allCards.filter((card) => {
		return permissions[card.requiredPermission] === true;
	});

	return allowedCards.map((card) => ({
		cardId: card.cardId,
		enabled: true,
		order: card.order, // Preserve original order from allCards
	}));
}

// Start server with database health check
async function startServer() {
	try {
		// Wait for database to be available
		await waitForDatabase(prisma);

		if (process.env.ENABLE_LOGGING === "true") {
			logger.info("✅ Database connection successful");
		}

		// Initialise settings on startup
		try {
			await initSettings();
			if (process.env.ENABLE_LOGGING === "true") {
				logger.info("✅ Settings initialised");
			}
		} catch (initError) {
			if (process.env.ENABLE_LOGGING === "true") {
				logger.error("❌ Failed to initialise settings:", initError.message);
			}
			throw initError; // Fail startup if settings can't be initialised
		}

		// Check and create default role permissions on startup
		await checkAndCreateRolePermissions();

		// Initialize dashboard preferences for all users
		await initializeDashboardPreferences();

		// Initialize BullMQ queue manager
		await queueManager.initialize();

		// Schedule recurring jobs
		await queueManager.scheduleAllJobs();

		// Set up Bull Board for queue monitoring
		const serverAdapter = new ExpressAdapter();
		// Set basePath to match where we mount the router
		serverAdapter.setBasePath("/bullboard");

		const { QUEUE_NAMES } = require("./services/automation");
		const bullAdapters = Object.values(QUEUE_NAMES).map(
			(queueName) => new BullMQAdapter(queueManager.queues[queueName]),
		);

		createBullBoard({
			queues: bullAdapters,
			serverAdapter: serverAdapter,
		});

		// Set the router for the Bull Board middleware (secured middleware above)
		bullBoardRouter = serverAdapter.getRouter();
		console.log("✅ Bull Board mounted at /bullboard (secured)");

		// Initialize WS layer with the underlying HTTP server
		initAgentWs(server, prisma);
		await agentVersionService.initialize();

		// Send metrics on startup (silent - no console output)
		try {
			const metricsReporting =
				queueManager.automations[QUEUE_NAMES.METRICS_REPORTING];
			await metricsReporting.sendSilent();
		} catch (_error) {
			// Silent failure - don't block server startup if metrics fail
		}

		server.listen(PORT, () => {
			if (process.env.ENABLE_LOGGING === "true") {
				logger.info(`Server running on port ${PORT}`);
				logger.info(`Environment: ${process.env.NODE_ENV}`);
			}
		});
	} catch (error) {
		console.error("❌ Failed to start server:", error.message);
		process.exit(1);
	}
}

startServer();

module.exports = app;
