/* global window, libsignal, textsecure */

// eslint-disable-next-line func-names
(function() {
  window.libloki = window.libloki || {};

  async function getPreKeyBundleForContact(pubKey) {
    const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair();
    const identityKey = myKeyPair.pubKey;

    // Retrieve ids. The ids stored are always the latest generated + 1
    const signedKeyId = textsecure.storage.get('signedKeyId', 2) - 1;

    const [signedKey, preKey] = await Promise.all([
      textsecure.storage.protocol.loadSignedPreKey(signedKeyId),
      new Promise(async resolve => {
        // retrieve existing prekey if we already generated one for that recipient
        const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact(
          pubKey
        );
        if (storedPreKey) {
          resolve({ pubKey: storedPreKey.pubKey, keyId: storedPreKey.keyId });
        } else {
          // generate and store new prekey
          const preKeyId = textsecure.storage.get('maxPreKeyId', 1);
          textsecure.storage.put('maxPreKeyId', preKeyId + 1);
          const newPreKey = await libsignal.KeyHelper.generatePreKey(preKeyId);
          await textsecure.storage.protocol.storePreKey(
            newPreKey.keyId,
            newPreKey.keyPair,
            pubKey
          );
          resolve({ pubKey: newPreKey.keyPair.pubKey, keyId: preKeyId });
        }
      }),
    ]);

    return {
      identityKey: new Uint8Array(identityKey),
      deviceId: 1, // TODO: fetch from somewhere
      preKeyId: preKey.keyId,
      signedKeyId,
      preKey: new Uint8Array(preKey.pubKey),
      signedKey: new Uint8Array(signedKey.pubKey),
      signature: new Uint8Array(signedKey.signature),
    };
  }
  async function removeContactPreKeyBundle(pubKey) {
    await Promise.all([
      textsecure.storage.protocol.removeContactPreKey(pubKey),
      textsecure.storage.protocol.removeContactSignedPreKey(pubKey),
    ]);
  }

  async function verifyFriendRequestAcceptPreKey(pubKey, buffer) {
    const storedPreKey = await textsecure.storage.protocol.loadPreKeyForContact(
      pubKey
    );
    if (!storedPreKey) {
      throw new Error(
        'Received a friend request from a pubkey for which no prekey bundle was created'
      );
    }
    // need to pop the version
    // eslint-disable-next-line no-unused-vars
    const version = buffer.readUint8();
    const preKeyProto = window.textsecure.protobuf.PreKeyWhisperMessage.decode(
      buffer
    );
    if (!preKeyProto) {
      throw new Error(
        'Could not decode PreKeyWhisperMessage while attempting to match the preKeyId'
      );
    }
    const { preKeyId } = preKeyProto;
    if (storedPreKey.keyId !== preKeyId) {
      throw new Error(
        'Received a preKeyWhisperMessage (friend request accept) from an unknown source'
      );
    }
  }

  function getGuardNodes() {
    return window.Signal.Data.getGuardNodes();
  }

  function updateGuardNodes(nodes) {
    return window.Signal.Data.updateGuardNodes(nodes);
  }

  window.libloki.storage = {
    getPreKeyBundleForContact,
    removeContactPreKeyBundle,
    verifyFriendRequestAcceptPreKey,
    getGuardNodes,
    updateGuardNodes,
  };

  // Libloki protocol store

  const store = window.SignalProtocolStore.prototype;

  store.storeContactPreKey = async (pubKey, preKey) => {
    const key = {
      // id: (autoincrement)
      identityKeyString: pubKey,
      publicKey: preKey.publicKey,
      keyId: preKey.keyId,
    };

    await window.Signal.Data.createOrUpdateContactPreKey(key);
  };

  store.loadContactPreKey = async pubKey => {
    const preKey = await window.Signal.Data.getContactPreKeyByIdentityKey(
      pubKey
    );
    if (preKey) {
      return {
        id: preKey.id,
        keyId: preKey.keyId,
        publicKey: preKey.publicKey,
        identityKeyString: preKey.identityKeyString,
      };
    }

    window.log.warn('Failed to fetch contact prekey:', pubKey);
    return undefined;
  };

  store.loadContactPreKeys = async filters => {
    const { keyId, identityKeyString } = filters;
    const keys = await window.Signal.Data.getContactPreKeys(
      keyId,
      identityKeyString
    );
    if (keys) {
      return keys.map(preKey => ({
        id: preKey.id,
        keyId: preKey.keyId,
        publicKey: preKey.publicKey,
        identityKeyString: preKey.identityKeyString,
      }));
    }

    window.log.warn('Failed to fetch signed prekey with filters', filters);
    return undefined;
  };

  store.removeContactPreKey = async pubKey => {
    await window.Signal.Data.removeContactPreKeyByIdentityKey(pubKey);
  };

  store.clearContactPreKeysStore = async () => {
    await window.Signal.Data.removeAllContactPreKeys();
  };

  store.storeContactSignedPreKey = async (pubKey, signedPreKey) => {
    const key = {
      // id: (autoincrement)
      identityKeyString: pubKey,
      keyId: signedPreKey.keyId,
      publicKey: signedPreKey.publicKey,
      signature: signedPreKey.signature,
      created_at: Date.now(),
      confirmed: false,
    };
    await window.Signal.Data.createOrUpdateContactSignedPreKey(key);
  };

  store.loadContactSignedPreKey = async pubKey => {
    const preKey = await window.Signal.Data.getContactSignedPreKeyByIdentityKey(
      pubKey
    );
    if (preKey) {
      return {
        id: preKey.id,
        identityKeyString: preKey.identityKeyString,
        publicKey: preKey.publicKey,
        signature: preKey.signature,
        created_at: preKey.created_at,
        keyId: preKey.keyId,
        confirmed: preKey.confirmed,
      };
    }
    window.log.warn('Failed to fetch contact signed prekey:', pubKey);
    return undefined;
  };

  store.loadContactSignedPreKeys = async filters => {
    const { keyId, identityKeyString } = filters;
    const keys = await window.Signal.Data.getContactSignedPreKeys(
      keyId,
      identityKeyString
    );
    if (keys) {
      return keys.map(preKey => ({
        id: preKey.id,
        identityKeyString: preKey.identityKeyString,
        publicKey: preKey.publicKey,
        signature: preKey.signature,
        created_at: preKey.created_at,
        keyId: preKey.keyId,
        confirmed: preKey.confirmed,
      }));
    }

    window.log.warn(
      'Failed to fetch contact signed prekey with filters',
      filters
    );
    return undefined;
  };

  store.removeContactSignedPreKey = async pubKey => {
    await window.Signal.Data.removeContactSignedPreKeyByIdentityKey(pubKey);
  };

  store.clearContactSignedPreKeysStore = async () => {
    await window.Signal.Data.removeAllContactSignedPreKeys();
  };
})();