const jwt = require("jsonwebtoken");
const { isEmptyObject, spliceObject } = require("@/utils/library");

export class JWT {
  constructor() {
    this.accessTokenSecret = process.env.ACCESS_TOKEN_SECRET;
    this.refreshTokenSecret = process.env.REFRESH_TOKEN_SECRET;
    this.accessTokenExpiration = process.env.ACCESS_TOKEN_EXPIRATION;
    this.refreshTokenExpiration = process.env.REFRESH_TOKEN_EXPIRATION;
    this.accessTokenMaxAge = process.env.ACCESS_TOKEN_MAXAGE || 60 * 60 * 24; // Default to 1 day
    this.refreshTokenMaxAge = process.env.REFRESH_TOKEN_MAXAGE || 60 * 60 * 24; // Default to 1 day
  }

  encodeSomething(payload, expiresIn) {
    return jwt.sign(payload, this.accessTokenSecret, { expiresIn });
  }

  /**
   * Generates an access token using JWT.
   *
   * @param {Object} payloads - The payload data containing user details.
   * @param {string} payloads.username - The username of the user.
   * @param {string} payloads.role - The role of the user.
   * @param {string} payloads.email - The email of the user.
   * @returns {string} - The generated JWT access token.
   * @throws {Error} - If the payload is empty or invalid.
   */
  generateAccessToken(payloads) {
    if (isEmptyObject(payloads)) throw new Error("payload must be filled");

    return jwt.sign(payloads, this.accessTokenSecret, {
      expiresIn: this.accessTokenExpiration,
    });
  }

  /**
   * Generates a refresh token using JWT.
   *
   * @param {Object} payloads - The payload data containing user details.
   * @param {number} payloads._id - The id of the user.
   * @param {string} payloads.username - The username of the user.
   * @returns {string} - The generated JWT refresh token.
   * @throws {Error} - If the payload is empty or invalid.
   */
  generateRefreshToken(payloads) {
    if (isEmptyObject(payloads)) throw new Error("payload must be filled");

    return jwt.sign(payloads, this.refreshTokenSecret, {
      expiresIn: this.refreshTokenExpiration,
    });
  }

  /**
   * Verifies a JWT token using either the access or refresh token secret.
   *
   * @param {Object} data - An object containing a single key-value pair where the key indicates the token type ('access' or 'refresh')
   *                        and the value is the token string to be verified.
   * @returns {Object} - The decoded token payload if verification is successful.
   * @throws {Error} - If the token is invalid or verification fails.
   */
  verifyToken(data) {
    const check = {
      access: (token) =>
        jwt.verify(token, this.accessTokenSecret, (err, decoded) => {
          if (err) throw err;
          return decoded;
        }),
      refresh: (token) =>
        jwt.verify(token, this.refreshTokenSecret, (err, decoded) => {
          if (err) throw err;
          return decoded;
        }),
    };

    return check[Object.keys(data)[0]](Object.values(data)[0]);
  }

  checkToken(data) {
    const check = {
      access: (token) =>
        jwt.verify(token, this.accessTokenSecret, (err, _) => !err),
      refresh: (token) =>
        jwt.verify(token, this.refreshTokenSecret, (err, _) => !err),
    };

    return check[Object.keys(data)[0]](Object.values(data)[0]);
  }

  /**
   * Generates a set of secure cookies containing JWT tokens.
   *
   * @param {Object} data - The data containing user details.
   * @param {Object} [role={ accessToken: [], refreshToken: [] }] - An object containing property names
   *        that match the key names in the data object. The property values should be an array of
   *        strings containing the keys in the data object that should be included in the token.
   * @param {Object} [expiresIn={ accessToken: 60 * 60 * 1, refreshToken: 60 * 60 * 24 }] - An object
   *        containing the maximum age of the cookies in seconds.
   * @returns {Object<{ cookie: Object[], token: Object}>} - An array of cookie objects containing the JWT tokens and their
   *         corresponding options.
   */
  generateCredentialCookie(
    data,
    role = { accessToken: [], refreshToken: [] },
    expiresIn = { accessToken: 60 * 60 * 24, refreshToken: 60 * 60 * 24 }
  ) {
    const temp = {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      path: "/",
    };

    const term = {
      accessToken:
        role.accessToken.length > 0 || !!role.accessToken
          ? this.generateAccessToken(
              spliceObject(data, role.accessToken).selected
            )
          : "",
      refreshToken:
        role.refreshToken?.length > 0 || !!role.refreshToken
          ? this.generateRefreshToken(
              spliceObject(data, role.refreshToken).selected
            )
          : "",
    };

    return {
      cookie: Object.entries(role)
        .filter(([_, value]) => value.length > 0)
        .map(([key, _]) => ({
          ...temp,
          name: key,
          maxAge: expiresIn[key],
          value: term[key],
        })),
      token: term,
    };
  }

  /**
   * Decodes a JWT token without verifying its signature.
   *
   * @param {string} token - The JWT token to decode.
   * @returns {Object} - The decoded token payload.
   */
  decodeToken(token) {
    return jwt.decode(token, { complete: true }).payload;
  }
}
