import express from "express";
import path from "path";
import cors, { type CorsOptions } from "cors";
import helmet from "helmet";
import compression from "compression";
import cookieParser from "cookie-parser";
import swaggerUi, { type JsonObject } from "swagger-ui-express";
import { handleErrors } from "@/api/middleware/handleErrors.js";
import { generalApiLimiter } from "@/api/middleware/rateLimiter.js";
import { sanitizeBody, sanitizeQuery } from "@/api/middleware/sanitization.js";
import { setupRoutes } from "@/config/routes.js";
import { InitializedControllers } from "@/config/controllers.js";
import { EnvConfig } from "@/domain/app-settings/app-settings.service.js";
import { createStatusPageCorsOrigin } from "@/api/middleware/statusPageCorsOrigin.js";
import { isPublicStatusPageApiPath } from "@/api/middleware/statusPagePublicApiPath.js";
import { createStatusPageDocumentCsp } from "@/api/middleware/statusPageDocumentCsp.js";
import { ApiServices } from "@/config/services.api.js";
import { logContext } from "./utils/logger/logContext.js";

export const createApp = ({
	apiServices: services,
	controllers,
	envSettings,
	frontendPath,
	openApiSpec,
}: {
	apiServices: ApiServices;
	controllers: InitializedControllers;
	envSettings: EnvConfig;
	frontendPath: string;
	openApiSpec: JsonObject;
}) => {
	const allowedOrigin = envSettings.clientHost;
	const app = express();
	const defaultCorsOptions: CorsOptions = {
		origin: allowedOrigin,
		methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
		allowedHeaders: ["Content-Type", "Authorization", "Accept-Language"],
		credentials: true,
		exposedHeaders: ["X-Request-Id"],
	};
	const publicStatusPageCorsOrigin = createStatusPageCorsOrigin(allowedOrigin, services.statusPagesRepository);

	const devMode = envSettings.nodeEnv === "development";
	app.use(services.logger.httpMiddleware());
	app.use(generalApiLimiter(devMode));

	app.use((req, res, next) => {
		const corsOptions = isPublicStatusPageApiPath(req.method, req.path)
			? { ...defaultCorsOptions, origin: publicStatusPageCorsOrigin }
			: defaultCorsOptions;

		return cors(corsOptions)(req, res, next);
	});

	app.use(createStatusPageDocumentCsp(allowedOrigin));

	// Client runtime config; registered before express.static so it shadows the
	// fallback config.js in the client build output
	const clientConfigScript = `window.__CHECKMATE_CONFIG__ = ${JSON.stringify(envSettings.clientConfig).replace(/</g, "\\u003c")};`;
	app.get("/config.js", (req, res) => {
		res.set("Cache-Control", "no-cache").type("application/javascript").send(clientConfigScript);
	});

	app.use(express.static(frontendPath));

	app.use("/api/v1/monitors/import/json", express.json({ limit: "10mb" }));
	app.use(express.json());
	app.use(cookieParser());

	app.use(sanitizeBody());
	app.use(sanitizeQuery());
	app.use((req, _res, next) => logContext.run({ reqId: String(req.id) }, next));

	app.use(
		helmet({
			hsts: false,
			contentSecurityPolicy: {
				useDefaults: true,
				directives: {
					upgradeInsecureRequests: null,
					"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
					"img-src": ["'self'", "data:", "blob:", "https://img.shields.io"],
					"object-src": ["'none'"],
					"base-uri": ["'self'"],
				},
			},
		})
	);
	app.use(
		compression({
			level: 6,
			threshold: 1024,
			filter: (req, res) => {
				if (req.headers["x-no-compression"]) {
					return false;
				}
				return compression.filter(req, res);
			},
		})
	);
	// Swagger UI — dynamically set server URL from request
	app.use("/api-docs", swaggerUi.serve, (req: express.Request, res: express.Response, next: express.NextFunction) => {
		const protocol = req.protocol;
		const host = req.get("host");
		const dynamicSpec = {
			...openApiSpec,
			servers: [
				{
					url: `${protocol}://${host}/api/v1`,
					description: "Current Server",
				},
				...openApiSpec.servers,
			],
		};
		swaggerUi.setup(dynamicSpec)(req, res, next);
	});

	app.use("/api/v1/health", (req, res) => {
		res.json({
			status: "OK",
		});
	});

	// Main app routes
	setupRoutes(app, controllers, services);

	// FE routes
	app.get("/{*splat}", (req, res) => {
		res.sendFile(path.join(frontendPath, "index.html"));
	});
	app.use(handleErrors(services.logger));
	return app;
};