import "reflect-metadata";
import type { Database } from "bun:sqlite";
import express, { type NextFunction, type Request, type Response } from "express";
import swaggerUi from "swagger-ui-express";
import { ValidateError } from "tsoa";
import { HttpError } from "./httpErrors";
import { openApiDocument } from "./openapi";
import { RegisterRoutes } from "./generated/routes";

function setCommonHeaders(response: Response) {
  response.setHeader("access-control-allow-origin", "*");
  response.setHeader("access-control-allow-methods", "GET,POST,PATCH,OPTIONS");
  response.setHeader("access-control-allow-headers", "content-type");
}

export function createApp(
  db: Database,
  options: {
    parseJson?: boolean;
  } = {},
) {
  const app = express();
  app.locals.db = db;

  app.use((request, response, next) => {
    setCommonHeaders(response);
    if (request.method === "OPTIONS") {
      response.status(200).json({ ok: true });
      return;
    }

    next();
  });
  if (options.parseJson ?? true) {
    app.use(express.json());
  }

  app.get("/openapi.json", (_request, response) => {
    response.json(openApiDocument);
  });
  app.get("/swagger.json", (_request, response) => {
    response.json(openApiDocument);
  });
  app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiDocument));

  RegisterRoutes(app);

  app.use((_request, response) => {
    setCommonHeaders(response);
    response.status(404).json({ error: "Route not found." });
  });

  app.use(
    (error: unknown, _request: Request, response: Response, next: NextFunction) => {
      if (error instanceof ValidateError) {
        setCommonHeaders(response);
        response.status(400).json({
          error: "Validation failed.",
          details: error.fields,
        });
        return;
      }

      if (error instanceof HttpError) {
        setCommonHeaders(response);
        response.status(error.status).json(error.body);
        return;
      }

      if (error instanceof Error) {
        setCommonHeaders(response);
        response.status(500).json({ error: "Internal server error." });
        return;
      }

      next();
    },
  );

  return app;
}
