import { Router } from "express";
import Stacker from "../../../middlewares/stacker";
import { GetUserMiddleware } from "../../../middlewares/user";
import TwoFactor, {
   TFATypes as TwoFATypes,
   IOTC,
} from "../../../../models/twofactor";
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
import moment = require("moment");
import { upgradeToken } from "../helper";
import Logging from "@hibas123/nodelogging";

import * as speakeasy from "speakeasy";
import * as qrcode from "qrcode";
import config from "../../../../config";

const OTCRoute = Router();

OTCRoute.post(
   "/",
   Stacker(GetUserMiddleware(true, true), async (req, res) => {
      const { type } = req.query;
      if (type === "create") {
         //Generating new
         let secret = speakeasy.generateSecret({
            name: config.core.name,
            issuer: config.core.name,
         });
         let twofactor = TwoFactor.new(<IOTC>{
            user: req.user._id,
            type: TwoFATypes.OTC,
            valid: false,
            data: secret.base32,
         });
         let dataurl = await qrcode.toDataURL(secret.otpauth_url);
         await TwoFactor.save(twofactor);
         res.json({
            image: dataurl,
            id: twofactor._id,
         });
      } else if (type === "validate") {
         // Checking code and marking as valid
         const { code, id } = req.body;
         Logging.debug(req.body, id);
         let twofactor: IOTC = await TwoFactor.findById(id);
         const err = () => {
            throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
         };
         if (
            !twofactor ||
            !twofactor.user.equals(req.user._id) ||
            twofactor.type !== TwoFATypes.OTC ||
            !twofactor.data ||
            twofactor.valid
         ) {
            Logging.debug("Not found or wrong user", twofactor);
            err();
         }

         if (twofactor.expires && moment().isAfter(moment(twofactor.expires))) {
            await TwoFactor.delete(twofactor);
            Logging.debug("Expired!", twofactor);
            err();
         }

         let valid = speakeasy.totp.verify({
            secret: twofactor.data,
            encoding: "base32",
            token: code,
         });

         if (valid) {
            twofactor.expires = undefined;
            twofactor.valid = true;
            await TwoFactor.save(twofactor);
            res.json({ success: true });
         } else {
            throw new RequestError("Invalid Code!", HttpStatusCode.BAD_REQUEST);
         }
      } else {
         throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
      }
   })
);

OTCRoute.put(
   "/",
   Stacker(
      GetUserMiddleware(true, false, undefined, false),
      async (req, res) => {
         let { login, special } = req.token;
         let { id, code } = req.body;
         let twofactor: IOTC = await TwoFactor.findById(id);

         if (
            !twofactor ||
            !twofactor.valid ||
            !twofactor.user.equals(req.user._id) ||
            twofactor.type !== TwoFATypes.OTC
         ) {
            throw new RequestError(
               "Invalid Method!",
               HttpStatusCode.BAD_REQUEST
            );
         }

         if (twofactor.expires && moment().isAfter(twofactor.expires)) {
            twofactor.valid = false;
            await TwoFactor.save(twofactor);
            throw new RequestError(
               "Invalid Method!",
               HttpStatusCode.BAD_REQUEST
            );
         }

         let valid = speakeasy.totp.verify({
            secret: twofactor.data,
            encoding: "base32",
            token: code,
         });

         if (valid) {
            let [login_exp, special_exp] = await Promise.all([
               upgradeToken(login),
               upgradeToken(special),
            ]);
            res.json({ success: true, login_exp, special_exp });
         } else {
            throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
         }
      }
   )
);

export default OTCRoute;
