import { Request, Response } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error.js";
import User from "../../models/user.js";
import Client from "../../models/client.js";
import {
   getAccessTokenJWT,
   getIDToken,
   AccessTokenJWTExp,
} from "../../helper/jwt.js";
import Stacker from "../middlewares/stacker.js";
import { GetClientAuthMiddleware } from "../middlewares/client.js";
import ClientCode from "../../models/client_code.js";
import Mail from "../../models/mail.js";
import { randomBytes } from "crypto";
import moment = require("moment");
// import { JWTExpDur } from "../../keys";
import RefreshToken from "../../models/refresh_token.js";
import { getEncryptionKey } from "../../helper/user_key.js";
import { refreshTokenValidTime } from "../../config.js";

// TODO:
/*
For example, the authorization server could employ refresh token
rotation in which a new refresh token is issued with every access
token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is
compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach.
*/

const RefreshTokenRoute = Stacker(
   GetClientAuthMiddleware(false, false, true),
   async (req: Request, res: Response) => {
      let grant_type = req.query.grant_type || req.body.grant_type;
      if (!grant_type || grant_type === "authorization_code") {
         let code = req.query.code || req.body.code;
         let nonce = req.query.nonce || req.body.nonce;

         let c = await ClientCode.findOne({ code: code });
         if (!c || moment(c.validTill).isBefore()) {
            throw new RequestError(
               req.__("Invalid code"),
               HttpStatusCode.BAD_REQUEST
            );
         }

         let client = await Client.findById(c.client);

         let user = await User.findById(c.user);
         let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));

         let token = RefreshToken.new({
            user: c.user,
            client: c.client,
            permissions: c.permissions,
            token: randomBytes(16).toString("hex"),
            valid: true,
            validTill: moment().add(refreshTokenValidTime).toDate(),
         });
         await RefreshToken.save(token);
         await ClientCode.delete(c);

         let mail = mails.find((e) => e.primary);
         if (!mail) mail = mails[0];

         res.json({
            refresh_token: token.token,
            token: token.token,
            access_token: await getAccessTokenJWT({
               client: client,
               user: user,
               permissions: c.permissions,
            }),
            token_type: "bearer",
            expires_in: AccessTokenJWTExp.asSeconds(),
            profile: {
               uid: user.uid,
               email: mail ? mail.mail : "",
               name: user.name,
               enc_key: getEncryptionKey(user, client),
            },
            id_token: getIDToken(user, client.client_id, nonce),
         });
      } else if (grant_type === "refresh_token") {
         let refresh_token = req.query.refresh_token || req.body.refresh_token;
         if (!refresh_token)
            throw new RequestError(
               req.__("refresh_token not set"),
               HttpStatusCode.BAD_REQUEST
            );

         let token = await RefreshToken.findOne({ token: refresh_token });
         if (!token || !token.valid || moment(token.validTill).isBefore())
            throw new RequestError(
               req.__("Invalid token"),
               HttpStatusCode.BAD_REQUEST
            );

         token.validTill = moment().add(refreshTokenValidTime).toDate();
         await RefreshToken.save(token);

         let user = await User.findById(token.user);
         let client = await Client.findById(token.client);
         let jwt = await getAccessTokenJWT({
            user,
            client,
            permissions: token.permissions,
         });
         res.json({
            access_token: jwt,
            expires_in: AccessTokenJWTExp.asSeconds(),
         });
      } else {
         throw new RequestError(
            "invalid grant_type",
            HttpStatusCode.BAD_REQUEST
         );
      }
   }
);

export default RefreshTokenRoute;
