/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2023 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import { ISodium } from "@notesnook/sodium";
import {
  EncryptionKey,
  EncryptionKeyPair,
  SerializedKey,
  SerializedKeyPair
} from "./types.js";

export enum base64_variants {
  ORIGINAL = 1,
  ORIGINAL_NO_PADDING = 3,
  URLSAFE = 5,
  URLSAFE_NO_PADDING = 7
}

export default class KeyUtils {
  static deriveKey(
    sodium: ISodium,
    password: string,
    salt?: string
  ): EncryptionKey {
    let saltBytes: Uint8Array;
    if (!salt)
      saltBytes = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
    else {
      saltBytes = sodium.from_base64(salt);
    }

    if (!saltBytes)
      throw new Error("Could not generate bytes from the given salt.");

    const key = sodium.crypto_pwhash(
      sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
      password,
      saltBytes,
      3, // operations limit
      1024 * 1024 * 8, // memory limit (8MB)
      sodium.crypto_pwhash_ALG_ARGON2I13
    );

    return {
      key,
      salt: typeof salt === "string" ? salt : sodium.to_base64(saltBytes)
    };
  }

  static deriveKeyPair(sodium: ISodium): EncryptionKeyPair {
    const keypair = sodium.crypto_box_keypair();
    return {
      publicKey: keypair.publicKey,
      privateKey: keypair.privateKey
    };
  }

  static exportKey(
    sodium: ISodium,
    password: string,
    salt?: string
  ): SerializedKey {
    const { key, salt: keySalt } = this.deriveKey(sodium, password, salt);
    return { key: sodium.to_base64(key), salt: keySalt };
  }

  static exportKeyPair(sodium: ISodium): SerializedKeyPair {
    const { publicKey, privateKey } = this.deriveKeyPair(sodium);
    return {
      publicKey: sodium.to_base64(publicKey),
      privateKey: sodium.to_base64(privateKey)
    };
  }

  /**
   * Takes in either a password or a serialized encryption key
   * and spits out a key that can be directly used for encryption/decryption.
   * @param input
   */
  static transform(sodium: ISodium, input: SerializedKey): EncryptionKey {
    if ("password" in input && !!input.password) {
      const { password, salt } = input;
      return this.deriveKey(sodium, password, salt);
    } else if ("key" in input && !!input.salt && !!input.key) {
      return { key: sodium.from_base64(input.key), salt: input.salt };
    }
    throw new Error("Invalid input.");
  }
}
