import cors from "@fastify/cors";
import multipart from "@fastify/multipart";
import sensible from "@fastify/sensible";
import fastify, { type FastifyInstance } from "fastify";
import { toErrorResponse } from "./lib/errors";
import { registerHealthRoutes } from "./routes/health";
import { registerImageRoutes } from "./routes/images";
import { registerSandboxRoutes } from "./routes/sandbox";
import { registerVolumeFsRoutes } from "./routes/volume-fs";
import { ImageService } from "./services/ImageService";
import { MicrosandboxRuntimeService } from "./services/MicrosandboxRuntimeService";
import { configureSwagger } from "./swagger";
import type { RuntimeService } from "./types/runtime-service";

export async function buildApp({
  runtimeService = new MicrosandboxRuntimeService(),
  imageService = new ImageService(),
  apiKey,
}: {
  runtimeService?: RuntimeService;
  imageService?: ImageService;
  apiKey?: string;
} = {}): Promise<FastifyInstance> {
  const app = fastify({
    logger: false,
    bodyLimit: Number(process.env.BODY_LIMIT) || 100 * 1024 * 1024,
  });

  app.register(cors, {
    delegator: (request, callback) => {
      callback(null, {
        origin: true,
        methods: request.headers["access-control-request-method"] ?? "*",
        allowedHeaders: request.headers["access-control-request-headers"],
      });
    },
  });
  app.register(sensible);
  app.register(multipart);

  if (apiKey) {
    app.addHook("onRequest", async (request, reply) => {
      if (request.method === "OPTIONS" || !request.url.startsWith("/api/")) {
        return;
      }

      if (request.headers.authorization !== `Bearer ${apiKey}`) {
        await reply.code(401).send({
          success: false,
          error: {
            code: "UNAUTHORIZED",
            message: "Unauthorized",
          },
        });
      }
    });
  }

  registerHealthRoutes(app);
  registerSandboxRoutes(app, runtimeService);
  registerImageRoutes(app, imageService);
  registerVolumeFsRoutes(app);

  app.setErrorHandler((error, _request, reply) => {
    const { statusCode, body } = toErrorResponse(error);
    reply.status(statusCode).send(body);
  });

  await configureSwagger(app);

  return app;
}
