import { Scalar } from "@scalar/hono-api-reference";
import type { Context, Next } from "hono";
import { Hono } from "hono";
import { bodyLimit } from "hono/body-limit";
import { cors } from "hono/cors";
import { secureHeaders } from "hono/secure-headers";
import { rateLimiter } from "hono-rate-limiter";
import { openAPIRouteHandler } from "hono-openapi";
import { authController } from "./modules/auth/auth.controller";
import { ssoController } from "./modules/sso/sso.controller";
import { requireAuth } from "./modules/auth/auth.middleware";
import { repositoriesController } from "./modules/repositories/repositories.controller";
import { systemController } from "./modules/system/system.controller";
import { volumeController } from "./modules/volumes/volume.controller";
import { backupScheduleController } from "./modules/backups/backups.controller";
import { eventsController } from "./modules/events/events.controller";
import { notificationsController } from "./modules/notifications/notifications.controller";
import { handleServiceError } from "./utils/errors";
import { logger } from "@zerobyte/core/node";
import { config } from "./core/config";
import { auth } from "~/server/lib/auth";
import { db } from "./db/db";

const requestLogger = async (c: Context, next: Next) => {
	const method = c.req.method;
	const path = c.req.path;
	const start = performance.now();

	logger.debug(`<-- ${method} ${path}`);

	try {
		await next();
	} finally {
		logger.debug(`--> ${method} ${path} ${c.res.status} ${Math.round(performance.now() - start)}ms`);
	}
};

const generalDescriptor = (app: Hono) =>
	openAPIRouteHandler(app, {
		documentation: {
			info: {
				title: "Zerobyte API",
				version: "1.0.0",
				description: "API for managing volumes",
			},
			servers: [{ url: `http://${config.serverIp}:4096`, description: "Development Server" }],
		},
	});

const scalarDescriptor = Scalar({
	title: "Zerobyte API Docs",
	pageTitle: "Zerobyte API Docs",
	url: "/api/v1/openapi.json",
});

export const createApp = () => {
	db.run("PRAGMA foreign_keys = ON;");
	const app = new Hono();

	if (config.trustedOrigins) {
		app.use(cors({ origin: config.trustedOrigins }));
	}

	if (config.environment === "production") {
		app.use(secureHeaders());
		app.use(requestLogger);
	}

	app.use(
		rateLimiter({
			windowMs: 60 * 5 * 1000,
			limit: 1000,
			keyGenerator: (c) => c.req.header("x-forwarded-for") ?? "",
			skip: () => {
				return config.flags.disableRateLimiting;
			},
		}),
	);

	app.use(
		bodyLimit({
			maxSize: 10 * 1024 * 1024, // 10MB
			onError: (c) => c.json({ message: "Request body too large" }, 413),
		}),
	);

	app
		.get("/api/healthcheck", (c) => c.json({ status: "ok" }))
		.route("/api/v1/auth", authController)
		.route("/api/v1/auth", ssoController)
		.route("/api/v1/volumes", volumeController)
		.route("/api/v1/repositories", repositoriesController)
		.route("/api/v1/backups", backupScheduleController)
		.route("/api/v1/notifications", notificationsController)
		.route("/api/v1/system", systemController)
		.route("/api/v1/events", eventsController);

	app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw));
	app.get("/api/v1/openapi.json", generalDescriptor(app));
	app.get("/api/v1/docs", requireAuth, scalarDescriptor);

	app.onError((err, c) => {
		const { status, message, details } = handleServiceError(err);

		logger.error(`${c.req.url}: ${message}`);

		if (err.cause instanceof Error) {
			logger.error(err.cause.message);
		}

		return c.json(details ? { message, details } : { message }, status as 500);
	});

	return app;
};
