/* global window, dcodeIO, textsecure */

// eslint-disable-next-line func-names
(function() {
  let server;
  const EXHAUSTED_SNODES_RETRY_DELAY = 5000;
  const NUM_CONCURRENT_CONNECTIONS = 3;

  function stringToArrayBufferBase64(string) {
    return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();
  }

  const Response = function Response(options) {
    this.verb = options.verb || options.type;
    this.path = options.path || options.url;
    this.body = options.body || options.data;
    this.success = options.success;
    this.error = options.error;
    this.id = options.id;

    if (this.id === undefined) {
      const bits = new Uint32Array(2);
      window.crypto.getRandomValues(bits);
      this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
    }

    if (this.body === undefined) {
      this.body = null;
    }
  };

  const IncomingHttpResponse = function IncomingHttpResponse(options) {
    const request = new Response(options);

    this.verb = request.verb;
    this.path = request.path;
    this.body = request.body;

    this.respond = (status, message) => {
      // Mock websocket response
      window.log.info(status, message);
    };
  };

  window.HttpResource = function HttpResource(_server, opts = {}) {
    server = _server;
    let { handleRequest } = opts;
    if (typeof handleRequest !== 'function') {
      handleRequest = request => request.respond(404, 'Not found');
    }
    let connected = true;
    this.calledStop = false;
    let resolveStopPolling;
    const stopPolling = new Promise(res => {
      resolveStopPolling = res;
    });

    this.handleMessage = (message, options = {}) => {
      try {
        const dataPlaintext = stringToArrayBufferBase64(message);
        const messageBuf = textsecure.protobuf.WebSocketMessage.decode(
          dataPlaintext
        );
        if (
          messageBuf.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST
        ) {
          handleRequest(
            new IncomingHttpResponse({
              verb: messageBuf.request.verb,
              path: messageBuf.request.path,
              body: messageBuf.request.body,
              id: messageBuf.request.id,
            }),
            options
          );
        }
      } catch (error) {
        const info = {
          message,
          error: error.message,
        };
        window.log.warn('HTTP-Resources Failed to handle message:', info);
      }
    };

    this.pollServer = async () => {
      // bg.connect calls mr connect after storage system is ready
      window.log.info('http-resource pollServer start');
      // This blocking call will return only when all attempts
      // at reaching snodes are exhausted or a DNS error occured
      try {
        await server.startLongPolling(
          NUM_CONCURRENT_CONNECTIONS,
          stopPolling,
          messages => {
            connected = true;
            messages.forEach(message => {
              this.handleMessage(message.data);
            });
          }
        );
      } catch (e) {
        // we'll try again anyway
        window.log.error(
          'http-resource pollServer error',
          e.code,
          e.message,
          e.stack
        );
      }
      connected = false;

      if (this.calledStop) {
        // don't restart
        return;
      }

      // Exhausted all our snodes urls, trying again later from scratch
      setTimeout(() => {
        window.log.info(
          `http-resource: Exhausted all our snodes urls, trying again in ${EXHAUSTED_SNODES_RETRY_DELAY /
            1000}s from scratch`
        );
        this.pollServer();
      }, EXHAUSTED_SNODES_RETRY_DELAY);
    };

    this.isConnected = function isConnected() {
      return connected;
    };

    this.close = () => {
      this.calledStop = true;
      resolveStopPolling(true);
    };
  };
})();
