import * as miscreant from "miscreant";
import { base64 } from "rfc4648";
import * as nacl from "tweetnacl";
//import keyutil from 'js-crypto-key-utils';

export class GlobalCrypto {
  public static AES_SIV_N0_128_256_encrypt(plain, key): Promise<Uint8Array> {
    return miscreant.SIV.importKey(key, "AES-SIV").then((extractedKey) => {
      return extractedKey.seal(plain, []);
    });
  }

  public static AES_SIV_N0_128_256_decrypt(cipher, key): Promise<Uint8Array> {
    return miscreant.SIV.importKey(key, "AES-SIV").then((extractedKey) => {
      return extractedKey.open(cipher, []);
    });
  }

  /**
   * The result will contain the nonce in the first 12 byte: [...nonce, ...encrypted]
   * @param plain
   * @param key
   * @param nonce - 12 byte length, generated by crypto.getRandomValues
   */
  public static AES_SIV_N96_128_256_encrypt(
    plain,
    key,
    nonce?
  ): Promise<Uint8Array> {
    if (nonce == undefined) {
      nonce = window.crypto.getRandomValues(new Uint8Array(12));
    } else {
      if (nonce.byteLength != 12) {
        throw "Invalid byte length for nonce: " + nonce.byteLength;
      }
    }
    return miscreant.SIV.importKey(key, "AES-SIV").then((extractedKey) => {
      return extractedKey.seal(plain, [nonce]).then((encrypted) => {
        return new Uint8Array([...nonce, ...encrypted]);
      });
    });
  }

  /**
   *
   * @param cipher - it should contain the nonce in the first 12 byte: [...nonce, ...encrypted]
   * @param key
   */
  public static AES_SIV_N96_128_256_decrypt(cipher, key): Promise<Uint8Array> {
    return miscreant.SIV.importKey(key, "AES-SIV").then((extractedKey) => {
      // the first 12 byte is the nonce
      return extractedKey.open(cipher.subarray(12, cipher.byteLength), [
        cipher.subarray(0, 12),
      ]);
    });
  }

  /**
   * Generate shared secret
   * @param otherPublicKey
   * @param secretKey
   */
  public static Curve25519_ECDH(otherPublicKey, secretKey): Uint8Array {
    return nacl.box.before(otherPublicKey, secretKey);
  }

  /**
   * Generate a keyPair for sign/verify via nacl
   * @param seed The server will give you only the seed format of the secret key. From that you can generate public/secret key
   * @return keyPair {publicKey, secretKey}
   */
  public static Ed25519_256_keyFromSeed(seed): nacl.SignKeyPair {
    return nacl.sign.keyPair.fromSeed(seed);
  }

  public static Ed25519_256_sign(plain, secretKey): Uint8Array {
    return nacl.sign.detached(plain, secretKey);
  }

  public static Ed25519_256_verify(plain, signature, publicKey): boolean {
    return nacl.sign.detached.verify(plain, signature, publicKey);
  }

  /**
   *
   * @param key
   * @param salt
   * @return Promise (deriveBits)
   */
  public static HKDF_SHA256_256(key, salt) {
    let algo: HkdfParams;
    algo = {
      name: "HKDF",
      hash: "SHA-256",
      salt: salt,
      info: new Uint8Array(0),
    };

    return window.crypto.subtle
      .importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"])
      .then((cryptoKey) => {
        return window.crypto.subtle
          .deriveBits(algo, cryptoKey, 256)
          .then((deriveBits) => {
            return new Uint8Array(deriveBits);
          });
      });
  }

  /**
   * crypto.subtle.digest SHA256
   * @param data Uint8Array binary data
   * @returns Promise
   */
  public static SHA256(data: Uint8Array | ArrayBuffer) {
    return crypto.subtle.digest("SHA-256", data);
  }

  /**
   * crypto.subtle.digest SHA512
   * @param data Uint8Array binary data
   * @returns Promise
   */
  public static SHA512(data: Uint8Array) {
    return crypto.subtle.digest("SHA-512", data);
  }

  /*public static RSA_OAEP_SHA256_encrypt(plain, publicKey):any{

        // we will get der from server, so if we can generate keyObj from this, we are OK
        let keyObj = new keyutil.Key('der', publicKey);
                   
        // now put it back to pem to use as native
        return keyObj.export("pem").then((pem)=>{  
                        
  
            let innerPem = pem.toString()
                .replace(`-----BEGIN PUBLIC KEY-----`, '')
                .replace(`-----END PUBLIC KEY-----`, '')
                .replace(/\n/g, '')
                .replace(/\r/g, '');
            console.log(innerPem)
            let innerBinPem = base64.parse(innerPem);

            return window.crypto.subtle.importKey(
                "spki",
                innerBinPem,
                {
                    name: "RSA-OAEP",
                    hash: "SHA-256"
                },
                true,
                ["encrypt"]
            ).then((publicCryptoKey)=>{
                
                return window.crypto.subtle.encrypt("RSA-OAEP", publicCryptoKey, plain).then((encryptedPlain)=>{
                    return new Uint8Array(encryptedPlain);
                })
            });
        });

    }

    public static RSA_OAEP_SHA256_decrypt(plain, privateKey):any{

        // we will get der from server, so if we can generate keyObj from this, we are OK
        let keyObj = new keyutil.Key('der', privateKey);
        
        // now put it back to pem to use as native
        return keyObj.export("pem").then((pem)=>{  

            let innerPem = pem.toString()
                .replace(`-----BEGIN PRIVATE KEY-----`, '')
                .replace(`-----END PRIVATE KEY-----`, '')
                .replace(/\n/g, '')
                .replace(/\r/g, '');
            
            let innerBinPem = base64.parse(innerPem);

            return window.crypto.subtle.importKey(
                "pkcs8",
                innerBinPem,
                {
                    name: "RSA-OAEP",
                    hash: "SHA-256"
                },
                true,
                ["decrypt"]
            ).then((secretCryptoKey)=>{
                return window.crypto.subtle.decrypt("RSA-OAEP", secretCryptoKey, plain).then((decryptedPlain)=>{
                    return new Uint8Array(decryptedPlain);
                });
            })
        });
    }*/

  public static PBKDF2(password, salt): any {
    return window.crypto.subtle
      .importKey(
        // pbkdf2 key containing the pw
        "raw",
        password,
        { name: "PBKDF2" },
        false,
        ["deriveKey"]
      )
      .then((baseKey) => {
        return window.crypto.subtle
          .deriveKey(
            // derive key from the pw
            {
              name: "PBKDF2",
              salt: salt,
              iterations: 200000,
              hash: "SHA-512",
            },
            baseKey,
            {
              name: "HMAC",
              hash: "SHA-512",
              length: 512,
            },
            true,
            ["sign"]
          )
          .then((aesKey) => {
            // export it in order to display it
            return window.crypto.subtle
              .exportKey("raw", aesKey)
              .then((keyBytes) => {
                return new Uint8Array(keyBytes);
              });
          });
      });
  }
}
