import {
  hexStringToUint8Array,
  hexToString,
  stringToHex,
  uint8ArrayToHexString
} from '.'
import { DecryptionError } from '../types/errors/DecryptionError'

const ENCRYPTION_ALGO_NAME = 'AES-GCM'

export const generateEncryptionKey = async () => {
  const cryptoKey = await window.crypto.subtle.generateKey(
    { name: ENCRYPTION_ALGO_NAME, length: 128 },
    true,
    ['encrypt', 'decrypt']
  )

  const key = await window.crypto.subtle.exportKey('jwk', cryptoKey)

  const jsonString = JSON.stringify(key)
  const hexKey = stringToHex(jsonString)

  const iv = uint8ArrayToHexString(
    window.crypto.getRandomValues(new Uint8Array(16))
  )

  return `${hexKey}?iv=${iv}`
}

export const importKey = async (key: string) => {
  const splittedKey = key.split('?iv=')
  const keyString = hexToString(splittedKey[0])
  const jsonWebKey = JSON.parse(keyString)
  const iv = hexStringToUint8Array(splittedKey[1])

  const cryptoKey = await window.crypto.subtle.importKey(
    'jwk',
    jsonWebKey,
    { name: ENCRYPTION_ALGO_NAME },
    true,
    ['encrypt', 'decrypt']
  )

  return { cryptoKey, iv }
}

export const encryptArrayBuffer = async (
  arrayBuffer: ArrayBuffer,
  key: string
) => {
  const { cryptoKey, iv } = await importKey(key)

  // Encrypt the data
  const encryptedData = await window.crypto.subtle.encrypt(
    { name: ENCRYPTION_ALGO_NAME, iv },
    cryptoKey,
    arrayBuffer
  )

  return encryptedData
}

export const decryptArrayBuffer = async (
  encryptedData: ArrayBuffer,
  key: string
) => {
  try {
    const { cryptoKey, iv } = await importKey(key)

    // Decrypt the data
    const decryptedData = await window.crypto.subtle.decrypt(
      { name: ENCRYPTION_ALGO_NAME, iv },
      cryptoKey,
      encryptedData
    )

    return decryptedData
  } catch (err) {
    throw new DecryptionError(err)
  }
}
