import * as path from "path";
import * as ofs from "fs";
import { createClient } from "redis";
import rateLimit from "express-rate-limit";
import * as slowDown from "express-slow-down";
import RedisStore from "rate-limit-redis";
import * as express from "express";
import * as compression from "compression";
import * as db from "./database/database";
import config from "../config";
import * as passport from "passport";

import * as connection from "./routes/connection";
import router from "./routes";
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule";
import { startWorker } from "./queue";
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";

function indexResponse(req: express.Request, res: express.Response) {
  if (
    req.params.repoId &&
    req.headers["accept"] &&
    req.headers["accept"].indexOf("text/html") == -1
  ) {
    const repoId = req.path.split("/")[2];
    // if it is not an html request, it assumes that the browser try to load a different type of resource
    return res.redirect(
      `/api/repo/${repoId}/file/${req.path.substring(
        req.path.indexOf(repoId) + repoId.length + 1
      )}`
    );
  }
  res.sendFile(path.resolve(__dirname, "..", "public", "index.html"));
}

export default async function start() {
  const app = express();
  app.use(express.json());

  app.use(compression());
  app.set("trust proxy", true);
  app.set("etag", "strong");

  app.get("/ip", (request, response) => response.send(request.ip));

  // handle session and connection
  app.use(connection.appSession);
  app.use(passport.initialize());
  app.use(passport.session());

  startWorker();

  const redisClient = createClient({
    socket: {
      host: config.REDIS_HOSTNAME,
      port: config.REDIS_PORT,
    },
  });
  redisClient.on("error", (err) => console.log("Redis Client Error", err));

  await redisClient.connect();

  const rate = rateLimit({
    store: new RedisStore({
      sendCommand: (...args: string[]) => redisClient.sendCommand(args),
    }),
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: config.RATE_LIMIT, // limit each IP
    standardHeaders: true,
    legacyHeaders: false,
  });
  const speedLimiter = slowDown({
    windowMs: 15 * 60 * 1000, // 15 minutes
    delayAfter: 50,
    delayMs: 150,
    maxDelayMs: 5000,
    headers: true,
  });

  app.use("/github", rate, speedLimiter, connection.router);

  // api routes
  const apiRouter = express.Router();
  app.use("/api", rate, apiRouter);

  apiRouter.use("/admin", router.admin);
  apiRouter.use("/options", router.option);
  apiRouter.use("/conferences", router.conference);
  apiRouter.use("/user", router.user);
  apiRouter.use("/repo", router.repositoryPublic);
  apiRouter.use("/repo", speedLimiter, router.file);
  apiRouter.use("/repo", speedLimiter, router.repositoryPrivate);
  apiRouter.use("/pr", speedLimiter, router.pullRequestPublic);
  apiRouter.use("/pr", speedLimiter, router.pullRequestPrivate);

  apiRouter.get("/message", async (_, res) => {
    if (ofs.existsSync("./message.txt")) {
      return res.sendFile(path.resolve(__dirname, "..", "message.txt"));
    }
    res.sendStatus(404);
  });

  apiRouter.get("/stat", async (_, res) => {
    const [nbRepositories, users, nbPageViews, nbPullRequests] =
      await Promise.all([
        AnonymizedRepositoryModel.estimatedDocumentCount(),
        AnonymizedRepositoryModel.distinct("owner"),
        AnonymizedRepositoryModel.collection
          .aggregate([
            {
              $group: { _id: null, total: { $sum: "$pageView" } },
            },
          ])
          .toArray(),
        AnonymizedPullRequestModel.estimatedDocumentCount(),
      ]);

    res.json({
      nbRepositories,
      nbUsers: users.length,
      nbPageViews: nbPageViews[0].total,
      nbPullRequests,
    });
  });

  // web view
  app.use("/w/", rate, speedLimiter, router.webview);

  app
    .get("/", indexResponse)
    .get("/404", indexResponse)
    .get("/anonymize", indexResponse)
    .get("/r/:repoId/?*", indexResponse)
    .get("/repository/:repoId/?*", indexResponse);

  app.use(
    express.static(path.join(__dirname, "..", "public"), {
      etag: true,
      lastModified: true,
      maxAge: 3600, // 1h
    })
  );

  app.get("*", indexResponse);

  // start schedules
  conferenceStatusCheck();
  repositoryStatusCheck();

  await db.connect();
  app.listen(config.PORT);
  console.log("Database connected and Server started on port: " + config.PORT);
}
