// NOTE: We do not use an IV since we generate a new keypair each time we use these routines.

async function deriveKey(privateKey, publicKey) {
  return crypto.subtle.deriveKey(
    { name: "ECDH", public: publicKey },
    privateKey,
    { name: "AES-CBC", length: 256 },
    true,
    ["encrypt", "decrypt"]
  );
}

async function publicKeyToString(key) {
  return JSON.stringify(await crypto.subtle.exportKey("jwk", key));
}

async function stringToPublicKey(s) {
  return await crypto.subtle.importKey("jwk", JSON.parse(s), { name: "ECDH", namedCurve: "P-256" }, true, []);
}

function stringToArrayBuffer(s) {
  const buf = new Uint8Array(s.length);

  for (let i = 0; i < s.length; i++) {
    buf[i] = s.charCodeAt(i);
  }

  return buf;
}

function arrayBufferToString(b) {
  const buf = new Uint8Array(b);
  let s = "";

  for (let i = 0; i < buf.byteLength; i++) {
    s += String.fromCharCode(buf[i]);
  }

  return s;
}

// This allows a single object to be passed encrypted from a receiver in a req -> response flow

// Requestor generates a public key and private key, and should send the public key to receiver.
export async function generateKeys() {
  const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
  const publicKeyString = await publicKeyToString(keyPair.publicKey);
  return { publicKeyString, privateKey: keyPair.privateKey };
}

// Receiver takes the public key from requestor and passes obj to get a response public key and the encrypted data to return.
export async function generatePublicKeyAndEncryptedObject(incomingPublicKeyString, obj) {
  const iv = new Uint8Array(16);
  const incomingPublicKey = await stringToPublicKey(incomingPublicKeyString);
  const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
  const publicKeyString = await publicKeyToString(keyPair.publicKey);
  const secret = await deriveKey(keyPair.privateKey, incomingPublicKey);

  const encryptedData = btoa(
    arrayBufferToString(
      await crypto.subtle.encrypt({ name: "AES-CBC", iv }, secret, stringToArrayBuffer(JSON.stringify(obj)))
    )
  );

  return { publicKeyString, encryptedData };
}

// Requestor then takes the receiver's public key, the private key (returned from generateKeys()), and the data from the receiver.
export async function decryptObject(publicKeyString, privateKey, base64value) {
  const iv = new Uint8Array(16);
  const publicKey = await stringToPublicKey(publicKeyString);
  const secret = await deriveKey(privateKey, publicKey);
  const ciphertext = stringToArrayBuffer(atob(base64value));
  const data = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, secret, ciphertext);
  return JSON.parse(arrayBufferToString(data));
}