import config, { WebConfig } from "./config.js";
import express from "express";
import { Express } from "express";

import Logging from "@hibas123/nodelogging";
import { Format } from "@hibas123/logging";

import bodyparser from "body-parser";
import cookieparser from "cookie-parser";
import session from "express-session";
import MongoStore from "connect-mongo";

import i18n from "i18n";
import ApiRouter from "./api/index.js";
import ViewRouter from "./views/index.js";
import RequestError, { HttpStatusCode } from "./helper/request_error.js";
import DB from "./database.js";
import promiseMiddleware from "./helper/promiseMiddleware.js";
import User from "./models/user.js";

export default class Web {
   server: Express;
   private port: number;

   constructor(config: WebConfig) {
      this.server = express();
      this.server.set("trust proxy", 1);
      this.port = Number(config.port);
      this.registerMiddleware();
      this.registerUserSession();
      this.registerEndpoints();
      this.registerErrorHandler();
   }

   listen() {
      this.server.listen(this.port, () => {
         Logging.log(`Server listening on port ${this.port}`);
      });
   }

   private registerMiddleware() {
      const sess = session({
         secret: config.core.secret,
         resave: false,
         saveUninitialized: false,
         store: MongoStore.create({
            client: DB.getClient() as any,
            dbName: DB.db.databaseName,
            collectionName: "sessions",
            autoRemove: "native",
            touchAfter: 60 * 60 * 24,
         }),
         cookie: {
            maxAge: 1000 * 60 * 60 * 24 * 30 * 6,
            secure: !config.core.dev,
            sameSite: "strict",
         }
      });

      this.server.use(sess as any) // FIXME: These types seem to be brokenb, but they shouldn't
      this.server.use(cookieparser());
      this.server.use(
         bodyparser.json(),
         bodyparser.urlencoded({ extended: true })
      );
      this.server.use(i18n.init);

      //Logging Middleware
      this.server.use((req, res, next) => {
         let start = process.hrtime();
         let finished = false;
         let to = false;
         let listener = () => {
            if (finished) return;
            finished = true;
            let td = process.hrtime(start);
            let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
            let resFormat: (arg: any) => any = (arg) => arg;
            if (res.statusCode >= 200 && res.statusCode < 300)
               resFormat = Format.green;
            //Green
            else if (res.statusCode === 304 || res.statusCode === 302)
               resFormat = Format.yellow;  //"\x1b[33m";
            else if (res.statusCode >= 400 && res.statusCode < 500)
               resFormat = Format.red; // "\x1b[36m";
            //Cyan
            else if (res.statusCode >= 500 && res.statusCode < 600)
               resFormat = Format.cyan //"\x1b[31m"; //Red

            let m = req.method;
            while (m.length < 4) m += " ";
            Logging.getChild("HTTP").log(
               `${m} ${req.originalUrl} ${(req as any).language || ""
               }`, resFormat(res.statusCode), `- ${time}ms`
            );
            res.removeListener("finish", listener);
         };
         res.on("finish", listener);
         setTimeout(() => {
            to = true;
            listener();
         }, 2000);
         next();
      });

      // Compression will be handled by the reverse proxy!
      // this.server.use(
      //    compression({
      //       filter: (req, res) => {
      //          if (req.headers["x-no-compression"]) {
      //             return false;
      //          }
      //          return compression.filter(req, res);
      //       },
      //    })
      // );
   }

   private registerEndpoints() {
      this.server.use("/api", ApiRouter);
      this.server.use("/", ViewRouter);
   }

   private registerErrorHandler() {
      this.server.use((error, req: express.Request, res, next) => {
         if (!(error instanceof RequestError)) {
            error = new RequestError(
               error.message,
               error.status || HttpStatusCode.INTERNAL_SERVER_ERROR,
               error.nolog || false
            );
         }

         if (error.status === 500 && !(<any>error).nolog) {
            Logging.error(error);
         } else {
            Logging.log("Responded with Error", error.status, error.message);
         }

         if (req.accepts(["json"])) {
            res.json_status = error.status || 500;
            res.json({
               error: error.message,
               status: error.status || 500,
               additional: error.additional,
            });
         } else res.status(error.status || 500).send(error.message);
      });
   }

   private registerUserSession() {
      this.server.use(promiseMiddleware(async (req, res, next) => {
         // if (!req.session.user_id) {
         //    if (req.cookies && req.cookies.login) {
         //       let token = await LoginToken.findOne({ token: req.cookies.login, valid: true });
         //       if (await CheckToken(token, true)) {
         //          req.session.user_id = token.user.toString();
         //       }
         //    }

         //    if (req.cookies && req.cookies.special) {
         //       let token = await LoginToken.findOne({ token: req.cookies.special, valid: true });
         //       if (await CheckToken(token, true)) {
         //          req.session.user_id = token.user.toString();
         //       }
         //    }
         // }

         if (req.session.user_id) {
            req.user = await User.findById(req.session.user_id);
            req.isAdmin = req.user.admin;
         }

         return next();
      }));
   }
}
