import jwt from 'jsonwebtoken';
import moment, { Moment } from 'moment';
import { Types } from 'mongoose';
import httpStatus from 'http-status';

import tokenTypes from '@/modules/token/token.types';
import { getUserByEmail } from '../user/user.service';
import ApiError from '@/shared/utils/errors/ApiError';
import { IUserDoc } from '@/modules/user/user.interfaces';
import config from '@/shared/config/config';
import {
  AccessAndRefreshTokens,
  IToken,
  TokenSubject,
} from '@/modules/token/token.interfaces';
import { getObjectId } from '@/shared/utils/commonHelper';
import { validateUserToken } from '../auth/auth.helper';
import responseCodes from '@/shared/utils/responseCode/responseCode';
const { UserResponseCodes } = responseCodes;

/**
 * Generate token
 * @param {Types.ObjectId} userId
 * @param {Moment} expires
 * @param {string} type
 * @param {string} [secret]
 * @returns {string}
 */
export const generateToken = (
  userId: Types.ObjectId,
  companyId: Types.ObjectId,
  expires: Moment,
  type: string,
  resetTokenVersion?: number,
  secret: string = config.jwt.secret,
): string => {
  const payload = {
    sub: userId,
    companyId,
    iat: moment().unix(),
    exp: expires.unix(),
    type,
    ...(resetTokenVersion && { resetTokenVersion }),
  };
  return jwt.sign(payload, secret);
};

/**
 * Verify token and return token doc (or throw an error if it is not valid)
 * @param {string} token
 * @returns {Promise< IToken>}
 */
export const verifyToken = async (token: string): Promise<IToken> => {
  try {
    const payload = jwt.verify(token, config.jwt.secret) as IToken;

    await validateUserToken(payload);
    return payload;
  } catch (error) {
    if (error instanceof ApiError) throw error;

    throw new ApiError(
      httpStatus.OK,
      'Token Invalid',
      true,
      '',
      UserResponseCodes.TOKEN_INVALID,
    );
  }
};

/**
 * Generate auth tokens
 * @param {IUserDoc} user
 * @returns {Promise<AccessAndRefreshTokens>}
 */
export const generateAuthTokens = async ({
  id,
  companyId,
}: TokenSubject): Promise<AccessAndRefreshTokens> => {
  const company = getObjectId(companyId);

  const accessTokenExpires = moment().add(
    config.jwt.accessExpirationMinutes,
    'minutes',
  );
  const accessToken = generateToken(
    id,
    company,
    accessTokenExpires,
    tokenTypes.ACCESS,
  );

  const refreshTokenExpires = moment().add(
    config.jwt.refreshExpirationDays,
    'days',
  );
  const refreshToken = generateToken(
    id,
    company,
    refreshTokenExpires,
    tokenTypes.REFRESH,
  );

  return {
    access: {
      token: accessToken,
      expires: accessTokenExpires.toDate(),
    },
    refresh: {
      token: refreshToken,
      expires: refreshTokenExpires.toDate(),
    },
  };
};

/**
 * Generate reset password token
 * @param {string} email
 * @returns {Promise<string>}
 */
export const generateResetPasswordToken = async (
  email: string,
): Promise<string> => {
  const user = await getUserByEmail(email);

  const expires = moment().add(
    config.jwt.resetPasswordExpirationMinutes,
    'minutes',
  );
  const resetPasswordToken = generateToken(
    user.id,
    user.company.id?._id,
    expires,
    tokenTypes.RESET_PASSWORD,
    user.resetTokenVersion,
  );

  return resetPasswordToken;
};

/**
 * Generate verify email token
 * @param {IUserDoc} user
 * @returns {Promise<string>}
 */
export const generateVerifyEmailToken = async (
  user: IUserDoc,
): Promise<string> => {
  const expires = moment().add(
    config.jwt.verifyEmailExpirationMinutes,
    'minutes',
  );
  const verifyEmailToken = generateToken(
    user.id,
    user.company.id?._id,
    expires,
    tokenTypes.VERIFY_EMAIL,
  );
  return verifyEmailToken;
};
