/* eslint-disable class-methods-use-this */
/* global window, Buffer, StringView, dcodeIO */

class LokiSnodeAPI {
  // ************** NOTE ***************
  // This is not used by anything yet,
  // but should be. Do not remove!!!
  // ***********************************
  async getLnsMapping(lnsName, timeout) {
    // Returns { pubkey, error }
    // pubkey is
    //      undefined when unconfirmed or no mapping found
    //      string    when found
    // timeout parameter optional (ms)

    // How many nodes to fetch data from?
    const numRequests = 5;

    // How many nodes must have the same response value?
    const numRequiredConfirms = 3;

    let ciphertextHex;
    let pubkey;
    let error;

    const _ = window.Lodash;

    const input = Buffer.from(lnsName);
    const output = await window.blake2b(input);
    const nameHash = dcodeIO.ByteBuffer.wrap(output).toString('base64');

    // Timeouts
    const maxTimeoutVal = 2 ** 31 - 1;
    const timeoutPromise = () =>
      new Promise((_resolve, reject) =>
        setTimeout(() => reject(), timeout || maxTimeoutVal)
      );

    // Get nodes capable of doing LNS
    const lnsNodes = await window.SnodePool.getNodesMinVersion(
      window.CONSTANTS.LNS_CAPABLE_NODES_VERSION
    );

    // Enough nodes?
    if (lnsNodes.length < numRequiredConfirms) {
      error = { lnsTooFewNodes: window.i18n('lnsTooFewNodes') };
      return { pubkey, error };
    }

    const confirmedNodes = [];

    // Promise is only resolved when a consensus is found
    let cipherResolve;
    const cipherPromise = () =>
      new Promise(resolve => {
        cipherResolve = resolve;
      });

    const decryptHex = async cipherHex => {
      const ciphertext = new Uint8Array(StringView.hexToArrayBuffer(cipherHex));

      const res = await window.decryptLnsEntry(lnsName, ciphertext);
      const publicKey = StringView.arrayBufferToHex(res);

      return publicKey;
    };

    const fetchFromNode = async node => {
      const res = await window.NewSnodeAPI._requestLnsMapping(node, nameHash);

      // Do validation
      if (res && res.result && res.result.status === 'OK') {
        const hasMapping = res.result.entries && res.result.entries.length > 0;

        const resValue = hasMapping
          ? res.result.entries[0].encrypted_value
          : null;

        confirmedNodes.push(resValue);

        if (confirmedNodes.length >= numRequiredConfirms) {
          if (ciphertextHex) {
            // Result already found, dont worry
            return;
          }

          const [winner, count] = _.maxBy(
            _.entries(_.countBy(confirmedNodes)),
            x => x[1]
          );

          if (count >= numRequiredConfirms) {
            ciphertextHex = winner === String(null) ? null : winner;

            // null represents no LNS mapping
            if (ciphertextHex === null) {
              error = { lnsMappingNotFound: window.i18n('lnsMappingNotFound') };
            }

            cipherResolve({ ciphertextHex });
          }
        }
      }
    };

    const nodes = lnsNodes.splice(0, numRequests);

    // Start fetching from nodes
    nodes.forEach(node => fetchFromNode(node));

    // Timeouts (optional parameter)
    // Wait for cipher to be found; race against timeout
    // eslint-disable-next-line more/no-then
    await Promise.race([cipherPromise, timeoutPromise].map(f => f()))
      .then(async () => {
        if (ciphertextHex !== null) {
          pubkey = await decryptHex(ciphertextHex);
        }
      })
      .catch(() => {
        error = { lnsLookupTimeout: window.i18n('lnsLookupTimeout') };
      });

    return { pubkey, error };
  }
}

module.exports = LokiSnodeAPI;