import crypto from "crypto";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import env from "../enviroment/env";

const maxAgeAccess = 60 * 1000 * 20 + 1000 * 60;
const maxAgeRefresh = 60 * 1000 * 60 * 24 * 30 + 1000 * 60;
const maxAgeAccessStreamVideo = 60 * 1000 * 60 * 24;

export interface CryptoUser {
  _id: any;
  email: string;
  password: string;
  privateKey?: string;
  publicKey?: string;
  emailVerified?: boolean;
}

export const encryptToken = (token: string, key: any, iv: any): string => {
  iv = Buffer.from(iv, "hex");

  const TOKEN_CIPHER_KEY = crypto.createHash("sha256").update(key).digest();
  const cipher = crypto.createCipheriv("aes-256-cbc", TOKEN_CIPHER_KEY, iv);
  const encryptedText = cipher.update(token);

  return Buffer.concat([encryptedText, cipher.final()]).toString("hex");
};

export const decryptToken = (
  encryptedToken: any,
  key: string,
  iv: any
): string => {
  encryptedToken = Buffer.from(encryptedToken, "hex");
  iv = Buffer.from(iv, "hex");

  const TOKEN_CIPHER_KEY = crypto.createHash("sha256").update(key).digest();
  const decipher = crypto.createDecipheriv("aes-256-cbc", TOKEN_CIPHER_KEY, iv);
  const tokenDecrypted = decipher.update(encryptedToken);

  return Buffer.concat([tokenDecrypted, decipher.final()]).toString();
};

export const getEncryptionKey = (user: any): Buffer | undefined => {
  try {
    const userPassword = user.password;
    const masterEncryptedText = user.privateKey as string;
    const masterPassword = env.key!;
    const iv = Buffer.from(user.publicKey as string, "hex");

    const USER_CIPHER_KEY = crypto
      .createHash("sha256")
      .update(userPassword)
      .digest();
    const MASTER_CIPHER_KEY = crypto
      .createHash("sha256")
      .update(masterPassword)
      .digest();

    const unhexMasterText = Buffer.from(masterEncryptedText, "hex");
    const masterDecipher = crypto.createDecipheriv(
      "aes-256-cbc",
      MASTER_CIPHER_KEY,
      iv
    );
    let masterDecrypted = masterDecipher.update(unhexMasterText);
    masterDecrypted = Buffer.concat([masterDecrypted, masterDecipher.final()]);

    const decipher = crypto.createDecipheriv("aes-256-cbc", USER_CIPHER_KEY, iv);
    let decrypted = decipher.update(masterDecrypted);
    decrypted = Buffer.concat([decrypted, decipher.final()]);

    return decrypted;
  } catch (e) {
    console.log("Get Encryption Key Error", e);
    return undefined;
  }
};

export const generateEncryptionKeyMaterial = (
  userPassword: string,
  randomKey: Buffer
): { privateKey: string; publicKey: string } => {
  const masterPassword = env.key!;
  const iv = crypto.randomBytes(16);

  const USER_CIPHER_KEY = crypto
    .createHash("sha256")
    .update(userPassword)
    .digest();
  const cipher = crypto.createCipheriv("aes-256-cbc", USER_CIPHER_KEY, iv);
  let encryptedText = cipher.update(randomKey);
  encryptedText = Buffer.concat([encryptedText, cipher.final()]);

  const MASTER_CIPHER_KEY = crypto
    .createHash("sha256")
    .update(masterPassword)
    .digest();
  const masterCipher = crypto.createCipheriv(
    "aes-256-cbc",
    MASTER_CIPHER_KEY,
    iv
  );
  const masterEncryptedText = masterCipher.update(encryptedText);

  const privateKey = Buffer.concat([
    masterEncryptedText,
    masterCipher.final(),
  ]).toString("hex");
  const publicKey = iv.toString("hex");

  return { privateKey, publicKey };
};

export const hashPassword = async (password: string): Promise<string> => {
  return bcrypt.hash(password, 8);
};

export const comparePassword = async (
  password: string,
  hash: string
): Promise<boolean> => {
  return bcrypt.compare(password, hash);
};

export const signAuthTokens = (
  user: any
): {
  accessToken: string;
  refreshToken: string;
  encryptedRefreshToken: string;
  iv: string;
  time: number;
} => {
  const iv = crypto.randomBytes(16);
  const time = new Date().getTime();

  const userObj = {
    _id: user._id,
    emailVerified: user.emailVerified,
    email: user.email,
  };

  const accessToken = jwt.sign({ user: userObj, iv }, env.passwordAccess!, {
    expiresIn: maxAgeAccess.toString(),
  });
  const refreshToken = jwt.sign(
    { _id: user._id.toString(), iv, time },
    env.passwordRefresh!,
    { expiresIn: maxAgeRefresh.toString() }
  );

  const encryptionKey = getEncryptionKey(user);
  const encryptedRefreshToken = encryptToken(refreshToken, encryptionKey, iv);

  return {
    accessToken,
    refreshToken,
    encryptedRefreshToken,
    iv: iv.toString("hex"),
    time,
  };
};

export const signStreamVideoToken = (
  user: any
): { token: string; encryptedToken: string; iv: string; time: number } => {
  const iv = crypto.randomBytes(16);
  const time = new Date().getTime();

  const token = jwt.sign(
    { _id: user._id.toString(), iv, time },
    env.passwordAccess!,
    { expiresIn: maxAgeAccessStreamVideo.toString() }
  );

  const encryptionKey = getEncryptionKey(user);
  const encryptedToken = encryptToken(token, encryptionKey, iv);

  return { token, encryptedToken, iv: iv.toString("hex"), time };
};

export const signTempAuthToken = (
  user: any
): { token: string; encryptedToken: string } => {
  const iv = crypto.randomBytes(16);
  const token = jwt.sign({ _id: user._id.toString(), iv }, env.passwordAccess!, {
    expiresIn: "3000ms",
  });

  const encryptionKey = getEncryptionKey(user);
  const encryptedToken = encryptToken(token, encryptionKey, iv);

  return { token, encryptedToken };
};

export const signEmailVerifyToken = (
  user: any
): { token: string; encryptedToken: string } => {
  const iv = crypto.randomBytes(16);
  const token = jwt.sign({ _id: user._id.toString(), iv }, env.passwordAccess!, {
    expiresIn: "1d",
  });

  const encryptionKey = getEncryptionKey(user);
  const encryptedToken = encryptToken(token, encryptionKey, iv);

  return { token, encryptedToken };
};

export const signPasswordResetToken = (
  user: any
): { token: string; encryptedToken: string } => {
  const iv = crypto.randomBytes(16);
  const token = jwt.sign({ _id: user._id.toString(), iv }, env.passwordAccess!, {
    expiresIn: "1h",
  });

  const encryptionKey = getEncryptionKey(user);
  const encryptedToken = encryptToken(token, encryptionKey, iv);

  return { token, encryptedToken };
};
