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

const U2FRoute = Router();

/**
 * Registerinf a new YubiKey
 */
U2FRoute.post(
   "/",
   Stacker(GetUserMiddleware(true, true), async (req, res) => {
      const { type } = req.query;
      if (type === "challenge") {
         const registrationRequest = u2f.request(config.core.url);

         let twofactor = TwoFactor.new(<IYubiKey>{
            user: req.user._id,
            type: TwoFATypes.WEBAUTHN,
            valid: false,
            data: {
               registration: registrationRequest,
            },
         });
         await TwoFactor.save(twofactor);
         res.json({
            request: registrationRequest,
            id: twofactor._id,
            appid: config.core.url,
         });
      } else {
         const { response, id } = req.body;
         Logging.debug(req.body, id);
         let twofactor: IYubiKey = 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.WEBAUTHN ||
            !twofactor.data.registration ||
            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();
         }

         const result = u2f.checkRegistration(
            twofactor.data.registration,
            response
         );

         if (result.successful) {
            twofactor.data = {
               keyHandle: result.keyHandle,
               publicKey: result.publicKey,
            };
            twofactor.expires = undefined;
            twofactor.valid = true;
            await TwoFactor.save(twofactor);
            res.json({ success: true });
         } else {
            throw new RequestError(
               result.errorMessage,
               HttpStatusCode.BAD_REQUEST
            );
         }
      }
   })
);

U2FRoute.get(
   "/",
   Stacker(
      GetUserMiddleware(true, false, undefined, false),
      async (req, res) => {
         let { login, special } = req.token;
         let twofactor: IYubiKey = await TwoFactor.findOne({
            user: req.user._id,
            type: TwoFATypes.WEBAUTHN,
            valid: true,
         });

         if (!twofactor) {
            throw new RequestError(
               "Invalid Method!",
               HttpStatusCode.BAD_REQUEST
            );
         }

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

         let request = u2f.request(config.core.url, twofactor.data.keyHandle);
         login.data = {
            type: "ykr",
            request,
         };
         let r;
         if (special) {
            special.data = login.data;
            r = LoginToken.save(special);
         }

         await Promise.all([r, LoginToken.save(login)]);
         res.json({ request });
      }
   )
);

U2FRoute.put(
   "/",
   Stacker(
      GetUserMiddleware(true, false, undefined, false),
      async (req, res) => {
         let { login, special } = req.token;
         let twofactor: IYubiKey = await TwoFactor.findOne({
            user: req.user._id,
            type: TwoFATypes.WEBAUTHN,
            valid: true,
         });

         let { response } = req.body;
         if (
            !twofactor ||
            !login.data ||
            login.data.type !== "ykr" ||
            (special && (!special.data || special.data.type !== "ykr"))
         ) {
            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 login_exp;
         let special_exp;
         let result = u2f.checkSignature(
            login.data.request,
            response,
            twofactor.data.publicKey
         );
         if (result.successful) {
            if (special) {
               let result = u2f.checkSignature(
                  special.data.request,
                  response,
                  twofactor.data.publicKey
               );
               if (result.successful) {
                  special_exp = await upgradeToken(special);
               } else {
                  throw new RequestError(
                     result.errorMessage,
                     HttpStatusCode.BAD_REQUEST
                  );
               }
            }
            login_exp = await upgradeToken(login);
         } else {
            throw new RequestError(
               result.errorMessage,
               HttpStatusCode.BAD_REQUEST
            );
         }
         res.json({ success: true, login_exp, special_exp });
      }
   )
);

export default U2FRoute;
