import express, {
  type NextFunction,
  type Request,
  type Response,
} from "express";
import { attachGraphQLClient } from "./middleware/graphql";
import commentRouter from "./routes/comment";
import userRouter from "./routes/user";
import authorRouter from "./routes/author";
import workRouter from "./routes/work";
import tagRouter from "./routes/tag";
import collectionRouter from "./routes/collection";
import blogRouter from "./routes/blog";
import statsRouter from "./routes/stats";
import { log, serveStatic, setupVite } from "./vite";
import { createServer } from "node:http";

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// Attach per-request GraphQL client
app.use(attachGraphQLClient);

app.use((req, res, next) => {
  const start = Date.now();
  const path = req.path;
  let capturedJsonResponse: unknown;

  const originalResJson = res.json;
  res.json = (bodyJson, ...args) => {
    capturedJsonResponse = bodyJson;
    return originalResJson.apply(res, [bodyJson, ...args]);
  };

  res.on("finish", () => {
    const duration = Date.now() - start;
    if (path.startsWith("/api")) {
      let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
      if (capturedJsonResponse) {
        logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
      }

      if (logLine.length > 80) {
        logLine = `${logLine.slice(0, 79)}…`;
      }

      log(logLine);
    }
  });

  next();
});

(async () => {
  // Domain routers (incremental migration from monolith)
  app.use("/api/users", userRouter);
  app.use("/api/authors", authorRouter);
  app.use("/api/works", workRouter);
  app.use("/api/tags", tagRouter);
  app.use("/api/comments", commentRouter);
  app.use("/api/collections", collectionRouter);
  app.use("/api/blog", blogRouter);
  app.use("/api/stats", statsRouter);
  // comments router already mounted earlier at /api/comments

  const server = createServer(app);

  app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
    const fallbackStatus = 500;
    interface StatusCarrier {
      status?: number;
      statusCode?: number;
    }
    const status =
      typeof err === "object" && err
        ? ((err as StatusCarrier).status ??
          (err as StatusCarrier).statusCode ??
          fallbackStatus)
        : fallbackStatus;
    const message =
      err instanceof Error ? err.message : "Internal Server Error";

    res.status(status).json({ message });
    throw err;
  });

  // importantly only setup vite in development and after
  // setting up all the other routes so the catch-all route
  // doesn't interfere with the other routes
  if (app.get("env") === "development") {
    await setupVite(app, server);
  } else {
    serveStatic(app);
  }

  // ALWAYS serve the app on port 5000
  // this serves both the API and the client.
  // It is the only port that is not firewalled.
  const port = 5000;
  server.listen(port, "0.0.0.0", () => {
    log(`serving on port ${port}`);
  });
})();
