import { concatByteArray } from '../utility';

// client email and salt-input restrictions/values
export const CLIENT_SALT_INPUT_LENGTH = 16;
const MAX_EMAIL_LENGTH = 200;
const STATIC_SALT_PREFIX = new TextEncoder().encode('clarabot.com');
const UNIFORM_CLIENT_SALT_LENGTH =
  Math.floor(
    (MAX_EMAIL_LENGTH + STATIC_SALT_PREFIX.byteLength + CLIENT_SALT_INPUT_LENGTH + 10) / 10
  ) * 10;
//UNIFORM_CLIENT_SALT_LENGTH =               ((MAX_EMAIL_LENGTH + len(STATIC_SALT_PREFIX)       + CLIENT_SALT_INPUT_LENGTH + 10) //10) * 10

export class AuthValueError extends Error {}

/**
 * Client only calculates this on its end at register or password change.
    For hash calculation the input is padded to be similar length for all accounts and values.
 */
export function uniform_login_salt_input(
  client_salt_input: Uint8Array,
  prefix: Uint8Array = STATIC_SALT_PREFIX
): Uint8Array {
  let repeat = UNIFORM_CLIENT_SALT_LENGTH - client_salt_input.byteLength - prefix.byteLength;
  let padding;
  if (repeat > 0) {
    padding = new TextEncoder().encode('C'.repeat(repeat));
  } else {
    padding = new Uint8Array([]);
  }
  return concatByteArray(prefix, padding, client_salt_input);
}

/**
 * Create the final salt by hashing the (uniform) input.
    Server endpoints should add random delay to further mitigate timing attack.
 */
export function auth_salt_hash_function(uniform_salt: Uint8Array): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    // TODO upgrade typescript, remove PromiseLike shell
    crypto.subtle
      .digest('SHA-256', uniform_salt)
      .then((digest) => {
        resolve(new Uint8Array(digest));
      })
      .catch(reject);
  });
}

/**
 * The computation of the salt must be similar for every user to mitigate timing attacks. To this end we add
    padding to it to have a specified and constant length.
    The server should add random delays to authenticating requests in addition.
 */
export function server_salt(
  client_salt_input: Uint8Array,
  prefix?: Uint8Array
): Promise<Uint8Array> {
  if (client_salt_input.byteLength != CLIENT_SALT_INPUT_LENGTH) {
    throw new AuthValueError(
      'Client-salt-input length is incorrect: ' + client_salt_input.byteLength
    );
  }
  if (prefix) {
    prefix = concatByteArray(prefix, STATIC_SALT_PREFIX);
  } else {
    prefix = STATIC_SALT_PREFIX;
  }

  let uniform_salt = uniform_login_salt_input(client_salt_input, prefix);
  if (uniform_salt.byteLength != UNIFORM_CLIENT_SALT_LENGTH) {
    throw new AuthValueError('Uniform-salt length is incorrect: ' + uniform_salt.byteLength);
  }
  return auth_salt_hash_function(uniform_salt).then((salt) => {
    if (salt.byteLength != 32) {
      // pragma: no cover
      throw new AuthValueError('Auth-salt length is incorrect: ' + salt.byteLength);
    }
    return salt;
  });
}

/**
 * This is done on the server side when the user is trying to log in. The received encoded password is only stored in
    hashed form in the database to mitigate pass-the-hash attack.
    NOTE: At registration this step can be done by the client. There is no benefit of the server doing it then.
 */
export function auth_key_hash_function(authentication_key: Uint8Array): Promise<Uint8Array> {
  return new Promise((resolve, reject) => {
    // TODO upgrade typescript, remove PromiseLike shell
    crypto.subtle
      .digest('SHA-256', authentication_key)
      .then((digest) => {
        resolve(new Uint8Array(digest.slice(0, 16)));
      })
      .catch(reject);
  });
}

/**
 * The reference calls this 'v' and is a component of the input to the hash function that creates the salt.
    For illegitimate account discovery the server will generate pseudo salts for failed login attempts. These
    salts will have a periodically (yearly) changing random input of the same size as 'v'. The reference refers
    to that as 'o'.
    This should be as least 128 bits. (16 bytes)
    The user *must* generate a new salt input when changing their password.
 */
export function generate_client_salt_input(): Uint8Array {
  return crypto.getRandomValues(new Uint8Array(CLIENT_SALT_INPUT_LENGTH));
}
