import { NextFunction, Request, Response } from "express";
import LoginToken, { CheckToken } from "../../models/login_token";
import Logging from "@hibas123/nodelogging";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
import promiseMiddleware from "../../helper/promiseMiddleware";

class Invalid extends Error {}

/**
 * Returns customized Middleware function, that could also be called directly
 * by code and will return true or false depending on the token. In the false
 * case it will also send error and redirect if json is not set
 * @param json Default false. Checks if requests wants an json or html for returning errors
 * @param special_required Default false. If true, a special token is required
 * @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in
 * @param validated Default true. If false, the token must not be validated
 */
export function GetUserMiddleware(
   json = false,
   special_required: boolean = false,
   redirect_uri?: string,
   validated = true
) {
   return promiseMiddleware(async function (
      req: Request,
      res: Response,
      next?: NextFunction
   ) {
      const invalid = (message: string) => {
         throw new Invalid(req.__(message));
      };
      try {
         let { login, special } = req.query as { [key: string]: string };
         if (!login) {
            login = req.cookies.login;
            special = req.cookies.special;
         }
         if (!login) invalid("No login token");
         if (!special && special_required) invalid("No special token");

         let token = await LoginToken.findOne({ token: login, valid: true });
         if (!(await CheckToken(token, validated)))
            invalid("Login token invalid");

         let user = await User.findById(token.user);
         if (!user) {
            token.valid = false;
            await LoginToken.save(token);
            invalid("Login token invalid");
         }

         let special_token;
         if (special) {
            Logging.debug("Special found");
            special_token = await LoginToken.findOne({
               token: special,
               special: true,
               valid: true,
               user: token.user,
            });
            if (!(await CheckToken(special_token, validated)))
               invalid("Special token invalid");
            req.special = true;
         }

         req.user = user;
         req.isAdmin = user.admin;
         req.token = {
            login: token,
            special: special_token,
         };

         if (next) next();
         return true;
      } catch (e) {
         if (e instanceof Invalid) {
            if (req.method === "GET" && !json) {
               res.status(HttpStatusCode.UNAUTHORIZED);
               res.redirect(
                  "/login?base64=true&state=" +
                     Buffer.from(
                        redirect_uri ? redirect_uri : req.originalUrl
                     ).toString("base64")
               );
            } else {
               throw new RequestError(
                  req.__(
                     "You are not logged in or your login is expired" +
                        ` (${e.message})`
                  ),
                  HttpStatusCode.UNAUTHORIZED,
                  undefined,
                  { auth: true }
               );
            }
         } else {
            if (next) next(e);
            else throw e;
         }
         return false;
      }
   });
}

export const UserMiddleware = GetUserMiddleware();
