/* global AUTH_CONFIG */
import { arrayToB64, b64ToArray } from './utils';

const encoder = new TextEncoder();
const decoder = new TextDecoder();

function getOtherInfo(enc) {
  const name = encoder.encode(enc);
  const length = 256;
  const buffer = new ArrayBuffer(name.length + 16);
  const dv = new DataView(buffer);
  const result = new Uint8Array(buffer);
  let i = 0;
  dv.setUint32(i, name.length);
  i += 4;
  result.set(name, i);
  i += name.length;
  dv.setUint32(i, 0);
  i += 4;
  dv.setUint32(i, 0);
  i += 4;
  dv.setUint32(i, length);
  return result;
}

function concat(b1, b2) {
  const result = new Uint8Array(b1.length + b2.length);
  result.set(b1, 0);
  result.set(b2, b1.length);
  return result;
}

async function concatKdf(key, enc) {
  if (key.length !== 32) {
    throw new Error('unsupported key length');
  }
  const otherInfo = getOtherInfo(enc);
  const buffer = new ArrayBuffer(4 + key.length + otherInfo.length);
  const dv = new DataView(buffer);
  const concat = new Uint8Array(buffer);
  dv.setUint32(0, 1);
  concat.set(key, 4);
  concat.set(otherInfo, key.length + 4);
  const result = await crypto.subtle.digest('SHA-256', concat);
  return new Uint8Array(result);
}

export async function prepareScopedBundleKey(storage) {
  const keys = await crypto.subtle.generateKey(
    {
      name: 'ECDH',
      namedCurve: 'P-256'
    },
    true,
    ['deriveBits']
  );
  const privateJwk = await crypto.subtle.exportKey('jwk', keys.privateKey);
  const publicJwk = await crypto.subtle.exportKey('jwk', keys.publicKey);
  const kid = await crypto.subtle.digest(
    'SHA-256',
    encoder.encode(JSON.stringify(publicJwk))
  );
  privateJwk.kid = kid;
  publicJwk.kid = kid;
  storage.set('scopedBundlePrivateKey', JSON.stringify(privateJwk));
  return arrayToB64(encoder.encode(JSON.stringify(publicJwk)));
}

export async function decryptBundle(storage, bundle) {
  const privateJwk = JSON.parse(storage.get('scopedBundlePrivateKey'));
  storage.remove('scopedBundlePrivateKey');
  const privateKey = await crypto.subtle.importKey(
    'jwk',
    privateJwk,
    {
      name: 'ECDH',
      namedCurve: 'P-256'
    },
    false,
    ['deriveBits']
  );
  const jweParts = bundle.split('.');
  if (jweParts.length !== 5) {
    throw new Error('invalid jwe');
  }
  const header = JSON.parse(decoder.decode(b64ToArray(jweParts[0])));
  const additionalData = encoder.encode(jweParts[0]);
  const iv = b64ToArray(jweParts[2]);
  const ciphertext = b64ToArray(jweParts[3]);
  const tag = b64ToArray(jweParts[4]);

  if (header.alg !== 'ECDH-ES' || header.enc !== 'A256GCM') {
    throw new Error('unsupported jwe type');
  }

  const publicKey = await crypto.subtle.importKey(
    'jwk',
    header.epk,
    {
      name: 'ECDH',
      namedCurve: 'P-256'
    },
    false,
    []
  );
  const sharedBits = await crypto.subtle.deriveBits(
    {
      name: 'ECDH',
      public: publicKey
    },
    privateKey,
    256
  );

  const rawSharedKey = await concatKdf(new Uint8Array(sharedBits), header.enc);
  const sharedKey = await crypto.subtle.importKey(
    'raw',
    rawSharedKey,
    {
      name: 'AES-GCM'
    },
    false,
    ['decrypt']
  );

  const plaintext = await crypto.subtle.decrypt(
    {
      name: 'AES-GCM',
      iv: iv,
      additionalData: additionalData,
      tagLength: tag.length * 8
    },
    sharedKey,
    concat(ciphertext, tag)
  );

  return JSON.parse(decoder.decode(plaintext));
}

export async function preparePkce(storage) {
  const verifier = arrayToB64(crypto.getRandomValues(new Uint8Array(64)));
  storage.set('pkceVerifier', verifier);
  const challenge = await crypto.subtle.digest(
    'SHA-256',
    encoder.encode(verifier)
  );
  return arrayToB64(new Uint8Array(challenge));
}

export async function deriveFileListKey(ikm) {
  const baseKey = await crypto.subtle.importKey(
    'raw',
    b64ToArray(ikm),
    { name: 'HKDF' },
    false,
    ['deriveKey']
  );
  const fileListKey = await crypto.subtle.deriveKey(
    {
      name: 'HKDF',
      salt: new Uint8Array(),
      info: encoder.encode('fileList'),
      hash: 'SHA-256'
    },
    baseKey,
    {
      name: 'AES-GCM',
      length: 128
    },
    true,
    ['encrypt', 'decrypt']
  );
  const rawFileListKey = await crypto.subtle.exportKey('raw', fileListKey);
  return arrayToB64(new Uint8Array(rawFileListKey));
}

export async function getFileListKey(storage, bundle) {
  const jwks = await decryptBundle(storage, bundle);
  const jwk = jwks[AUTH_CONFIG.key_scope];
  return deriveFileListKey(jwk.k);
}
