import srp from 'srp-js';
import * as crypt from './crypt';
import * as util from '../common/fetch';

/** Create a new session for the user */
export async function login (rawEmail, rawPassphrase) {
  // ~~~~~~~~~~~~~~~ //
  // Sanitize Inputs //
  // ~~~~~~~~~~~~~~~ //

  const email = _sanitizeEmail(rawEmail);
  const passphrase = _sanitizePassphrase(rawPassphrase);

  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
  // Fetch Salt and Submit A To Server //
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //

  const {saltKey, saltAuth} = await util.post('/auth/login-s', {email});
  const authSecret = await crypt.deriveKey(passphrase, email, saltKey);
  const secret1 = await crypt.srpGenKey();
  const c = new srp.Client(
    _getSrpParams(),
    Buffer.from(saltAuth, 'hex'),
    Buffer.from(email, 'utf8'),
    Buffer.from(authSecret, 'hex'),
    Buffer.from(secret1, 'hex')
  );
  const srpA = c.computeA().toString('hex');
  const {sessionStarterId, srpB} = await util.post('/auth/login-a', {srpA, email});

  // ~~~~~~~~~~~~~~~~~~~~~ //
  // Compute and Submit M1 //
  // ~~~~~~~~~~~~~~~~~~~~~ //

  c.setB(Buffer.from(srpB, 'hex'));
  const srpM1 = c.computeM1().toString('hex');
  const {srpM2} = await util.post('/auth/login-m1', {srpM1, sessionStarterId});

  // ~~~~~~~~~~~~~~~~~~~~~~~~~ //
  // Verify Server Identity M2 //
  // ~~~~~~~~~~~~~~~~~~~~~~~~~ //

  c.checkM2(Buffer.from(srpM2, 'hex'));

  // ~~~~~~~~~~~~~~~~~~~~~~ //
  // Initialize the Session //
  // ~~~~~~~~~~~~~~~~~~~~~~ //

  // Compute K (used for session ID)
  const sessionId = c.computeK().toString('hex');

  // Get and store some extra info (salts and keys)
  const {
    publicKey,
    encPrivateKey,
    encSymmetricKey,
    saltEnc,
    accountId,
    firstName,
    lastName
  } = await whoami(sessionId);

  const derivedSymmetricKey = await crypt.deriveKey(passphrase, email, saltEnc);
  const symmetricKeyStr = await crypt.decryptAES(derivedSymmetricKey, JSON.parse(encSymmetricKey));

  // Store the information for later
  setSessionData(
    sessionId,
    accountId,
    firstName,
    lastName,
    email,
    JSON.parse(symmetricKeyStr),
    JSON.parse(publicKey),
    JSON.parse(encPrivateKey)
  );
}

export function syncCreateResourceGroup (parentResourceId, name, encSymmetricKey) {
  return util.post('/api/resource_groups', {parentResourceId, name, encSymmetricKey});
}

export function syncGetResourceGroup (id) {
  return util.get(`/api/resource_groups/${id}`);
}

export function syncPull (body) {
  return util.post('/sync/pull', body);
}

export function syncPush (body) {
  return util.post('/sync/push', body);
}

export function syncResetData () {
  return util.post('/auth/reset');
}

export function syncFixDupes (resourceGroupIds) {
  return util.post('/sync/fix-dupes', {ids: resourceGroupIds});
}

export function unshareWithAllTeams (resourceGroupId) {
  return util.put(`/api/resource_groups/${resourceGroupId}/unshare`);
}

export async function shareWithTeam (resourceGroupId, teamId, rawPassphrase) {
  // Ask the server what we need to do to invite the member
  const instructions = await util.post(`/api/resource_groups/${resourceGroupId}/share-a`, {teamId});

  // Compute keys necessary to invite the member
  const passPhrase = _sanitizePassphrase(rawPassphrase);
  const {email, saltEnc, encPrivateKey, encSymmetricKey} = await whoami();
  const secret = await crypt.deriveKey(passPhrase, email, saltEnc);
  let symmetricKey;
  try {
    symmetricKey = crypt.decryptAES(secret, JSON.parse(encSymmetricKey));
  } catch (err) {
    throw new Error('Invalid password');
  }
  const privateKey = crypt.decryptAES(JSON.parse(symmetricKey), JSON.parse(encPrivateKey));
  const privateKeyJWK = JSON.parse(privateKey);
  const resourceGroupSymmetricKey = crypt.decryptRSAWithJWK(
    privateKeyJWK,
    instructions.encSymmetricKey
  );

  // Build the invite data request
  const newKeys = {};
  for (const accountId of Object.keys(instructions.keys)) {
    const accountPublicKeyJWK = JSON.parse(instructions.keys[accountId]);
    newKeys[accountId] = crypt.encryptRSAWithJWK(
      accountPublicKeyJWK,
      resourceGroupSymmetricKey
    );
  }

  // Actually share it with the team
  await util.post(`/api/resource_groups/${resourceGroupId}/share-b`, {teamId, keys: newKeys});
}

export function getPublicKey () {
  return getSessionData().publicKey;
}

export function getPrivateKey () {
  const {symmetricKey, encPrivateKey} = getSessionData();
  const privateKeyStr = crypt.decryptAES(symmetricKey, encPrivateKey);
  return JSON.parse(privateKeyStr);
}

export function getCurrentSessionId () {
  if (window) {
    return window.localStorage.getItem('currentSessionId');
  } else {
    return false;
  }
}

export function getAccountId () {
  return getSessionData().accountId;
}

export function getEmail () {
  return getSessionData().email;
}

export function getFirstName () {
  return getSessionData().firstName;
}

export function getFullName () {
  const {firstName, lastName} = getSessionData();
  return `${firstName || ''} ${lastName || ''}`.trim();
}

/**
 * get Data about the current session
 * @returns Object
 */
export function getSessionData () {
  const sessionId = getCurrentSessionId();
  if (!sessionId || !window) {
    return {};
  }

  const dataStr = window.localStorage.getItem(getSessionKey(sessionId));
  return JSON.parse(dataStr);
}

/** Set data for the new session and store it encrypted with the sessionId */
export function setSessionData (sessionId,
                                accountId,
                                firstName,
                                lastName,
                                email,
                                symmetricKey,
                                publicKey,
                                encPrivateKey) {
  const dataStr = JSON.stringify({
    id: sessionId,
    accountId: accountId,
    symmetricKey: symmetricKey,
    publicKey: publicKey,
    encPrivateKey: encPrivateKey,
    email: email,
    firstName: firstName,
    lastName: lastName
  });

  window.localStorage.setItem(getSessionKey(sessionId), dataStr);

  // NOTE: We're setting this last because the stuff above might fail
  window.localStorage.setItem('currentSessionId', sessionId);
}

/** Unset the session data (log out) */
export function unsetSessionData () {
  const sessionId = getCurrentSessionId();
  window.localStorage.removeItem(getSessionKey(sessionId));
  window.localStorage.removeItem(`currentSessionId`);
}

/** Check if we (think) we have a session */
export function isLoggedIn () {
  return getCurrentSessionId();
}

/** Log out and delete session data */
export async function logout () {
  try {
    await util.post('/auth/logout');
  } catch (e) {
    // Not a huge deal if this fails, but we don't want it to prevent the
    // user from signing out.
    console.warn('Failed to logout', e);
  }

  unsetSessionData();
}

export async function listTeams () {
  return util.get('/api/teams');
}

export async function endTrial () {
  await util.put('/api/billing/end-trial');
}

export function whoami (sessionId = null) {
  return util.get('/auth/whoami', sessionId);
}

export function getSessionKey (sessionId) {
  return `session__${(sessionId || '').slice(0, 10)}`;
}

// ~~~~~~~~~~~~~~~~ //
// Helper Functions //
// ~~~~~~~~~~~~~~~~ //

function _getSrpParams () {
  return srp.params[2048];
}

function _sanitizeEmail (email) {
  return email.trim().toLowerCase();
}

function _sanitizePassphrase (passphrase) {
  return passphrase.trim().normalize('NFKD');
}
