import { compare, hash } from "bcryptjs";
import type { NextApiRequest } from "next";
import type { Session } from "next-auth";
import {
  getSession as getSessionInner,
  GetSessionParams,
} from "next-auth/react";

import { HttpError } from "@documenso/lib/server";

export async function hashPassword(password: string) {
  const hashedPassword = await hash(password, 12);
  return hashedPassword;
}

export async function verifyPassword(password: string, hashedPassword: string) {
  const isValid = await compare(password, hashedPassword);
  return isValid;
}

export function validPassword(password: string) {
  if (password.length < 7) return false;

  if (!/[A-Z]/.test(password) || !/[a-z]/.test(password)) return false;

  if (!/\d+/.test(password)) return false;

  return true;
}

export async function getSession(
  options: GetSessionParams
): Promise<Session | null> {
  const session = await getSessionInner(options);

  // that these are equal are ensured in `[...nextauth]`'s callback
  return session as Session | null;
}

export function isPasswordValid(password: string): boolean;
export function isPasswordValid(
  password: string,
  breakdown: boolean,
  strict?: boolean
): { caplow: boolean; num: boolean; min: boolean; admin_min: boolean };
export function isPasswordValid(
  password: string,
  breakdown?: boolean,
  strict?: boolean
) {
  let cap = false, // Has uppercase characters
    low = false, // Has lowercase characters
    num = false, // At least one number
    min = false, // Eight characters, or fifteen in strict mode.
    admin_min = false;
  if (password.length > 7 && (!strict || password.length > 14)) min = true;
  if (strict && password.length > 14) admin_min = true;
  for (let i = 0; i < password.length; i++) {
    if (!isNaN(parseInt(password[i]))) num = true;
    else {
      if (password[i] === password[i].toUpperCase()) cap = true;
      if (password[i] === password[i].toLowerCase()) low = true;
    }
  }

  if (!breakdown)
    return cap && low && num && min && (strict ? admin_min : true);

  let errors: Record<string, boolean> = { caplow: cap && low, num, min };
  // Only return the admin key if strict mode is enabled.
  if (strict) errors = { ...errors, admin_min };

  return errors;
}

type CtxOrReq =
  | { req: NextApiRequest; ctx?: never }
  | { ctx: { req: NextApiRequest }; req?: never };

export const ensureSession = async (ctxOrReq: CtxOrReq) => {
  const session = await getSession(ctxOrReq);
  if (!session?.user)
    throw new HttpError({ statusCode: 401, message: "Unauthorized" });
  return session;
};

export enum ErrorCode {
  UserNotFound = "user-not-found",
  IncorrectPassword = "incorrect-password",
  UserMissingPassword = "missing-password",
  TwoFactorDisabled = "two-factor-disabled",
  TwoFactorAlreadyEnabled = "two-factor-already-enabled",
  TwoFactorSetupRequired = "two-factor-setup-required",
  SecondFactorRequired = "second-factor-required",
  IncorrectTwoFactorCode = "incorrect-two-factor-code",
  InternalServerError = "internal-server-error",
  NewPasswordMatchesOld = "new-password-matches-old",
  ThirdPartyIdentityProviderEnabled = "third-party-identity-provider-enabled",
  RateLimitExceeded = "rate-limit-exceeded",
  SocialIdentityProviderRequired = "social-identity-provider-required",
}
