/* global log, window, process, URL, dcodeIO */
const EventEmitter = require('events');
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');

const nodeFetch = require('node-fetch');

const validOpenGroupServer = async serverUrl => {
  // test to make sure it's online (and maybe has a valid SSL cert)
  try {
    const url = new URL(serverUrl);

    if (window.lokiFeatureFlags.useFileOnionRequests) {
      // check for LSRPC

      // this is safe (as long as node's in your trust model)
      // because
      const result = await window.tokenlessFileServerAdnAPI.serverRequest(
        `loki/v1/getOpenGroupKey/${url.hostname}`
      );
      if (result.response.meta.code === 200) {
        // supports it
        const obj = JSON.parse(result.response.data);
        const pubKey = dcodeIO.ByteBuffer.wrap(
          obj.data,
          'base64'
        ).toArrayBuffer();
        // verify it works...
        // get around the FILESERVER_HOSTS filter by not using serverRequest
        const res = await LokiAppDotNetAPI.sendViaOnion(
          pubKey,
          url,
          { method: 'GET' },
          { noJson: true }
        );
        if (res.result.status === 200) {
          log.info(
            `loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
          );
          // save pubkey for use...
          window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
          return true;
        }
        // otherwise fall back
      } else if (result.response.meta.code !== 404) {
        // unknown error code
        log.warn(
          'loki_public_chat::validOpenGroupServer - unknown error code',
          result.response.meta
        );
      }
    }
    // doesn't support it, fallback
    log.info(
      `loki_public_chat::validOpenGroupServer - directly contacting ${url.toString()}`
    );

    // allow .loki (may only need an agent but not sure
    //              until we have a .loki to test with)
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i)
      ? '0'
      : '1';
    await nodeFetch(serverUrl);
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
    // const txt = await res.text();
  } catch (e) {
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
    log.warn(
      `loki_public_chat::validOpenGroupServer - failing to create ${serverUrl}`,
      e.code,
      e.message
    );
    // bail out if not valid enough
    return false;
  }
  return true;
};

class LokiPublicChatFactoryAPI extends EventEmitter {
  constructor(ourKey) {
    super();
    this.ourKey = ourKey;
    this.servers = [];
    this.allMembers = [];
    this.openGroupPubKeys = {};
    // Multidevice states
    this.primaryUserProfileName = {};
  }

  // MessageReceiver.connect calls this
  // start polling in all existing registered channels
  async open() {
    await Promise.all(this.servers.map(server => server.open()));
  }

  // MessageReceiver.close
  async close() {
    await Promise.all(this.servers.map(server => server.close()));
  }

  // server getter/factory
  async findOrCreateServer(serverUrl) {
    let thisServer = this.servers.find(
      server => server.baseServerUrl === serverUrl
    );
    if (!thisServer) {
      log.info(`loki_public_chat::findOrCreateServer - creating ${serverUrl}`);

      const serverIsValid = await validOpenGroupServer(serverUrl);
      if (!serverIsValid) {
        // FIXME: add toast?
        log.error(
          `loki_public_chat::findOrCreateServer - error: ${serverUrl} is not valid`
        );
        return null;
      }

      // after verification then we can start up all the pollers
      if (process.env.USE_STUBBED_NETWORK) {
        // eslint-disable-next-line global-require
        const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js');
        thisServer = new StubAppDotNetAPI(this.ourKey, serverUrl);
      } else {
        thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl);
        if (this.openGroupPubKeys[serverUrl]) {
          thisServer.getPubKeyForUrl();
          if (!thisServer.pubKeyHex) {
            log.warn(
              `loki_public_chat::findOrCreateServer - failed to set public key`
            );
          }
        }
      }

      const gotToken = await thisServer.getOrRefreshServerToken();
      if (!gotToken) {
        log.warn(
          `loki_public_chat::findOrCreateServer - Invalid server ${serverUrl}`
        );
        return null;
      }
      if (window.isDev) {
        log.info(
          `loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
        );
      }

      this.servers.push(thisServer);
    }
    return thisServer;
  }

  // channel getter/factory
  async findOrCreateChannel(serverUrl, channelId, conversationId) {
    const server = await this.findOrCreateServer(serverUrl);
    if (!server) {
      log.error(`Failed to create server for: ${serverUrl}`);
      return null;
    }
    return server.findOrCreateChannel(this, channelId, conversationId);
  }

  // deallocate resources server uses
  unregisterChannel(serverUrl, channelId) {
    const i = this.servers.findIndex(
      server => server.baseServerUrl === serverUrl
    );
    if (i === -1) {
      log.warn(`Tried to unregister from nonexistent server ${serverUrl}`);
      return;
    }
    const thisServer = this.servers[i];
    if (!thisServer) {
      log.warn(`Tried to unregister from nonexistent server ${i}`);
      return;
    }
    thisServer.unregisterChannel(channelId);
    this.servers.splice(i, 1);
  }

  // shouldn't this be scoped per conversation?
  async getListOfMembers() {
    // enable in the next release
    /*
    let members = [];
    await Promise.all(this.servers.map(async server => {
      await Promise.all(server.channels.map(async channel => {
        const newMembers = await channel.getSubscribers();
        members = [...members, ...newMembers];
      }));
    }));
    const results = members.map(member => {
      return { authorPhoneNumber: member.username };
    });
    */
    return this.allMembers;
  }

  // TODO: make this private (or remove altogether) when
  // we switch to polling the server for group members
  setListOfMembers(members) {
    this.allMembers = members;
  }

  async setProfileName(profileName) {
    await Promise.all(
      this.servers.map(async server => {
        await server.setProfileName(profileName);
      })
    );
  }

  async setHomeServer(homeServer) {
    await Promise.all(
      this.servers.map(async server => {
        // this may fail
        // but we can't create a sql table to remember to retry forever
        // I think we just silently fail for now
        await server.setHomeServer(homeServer);
      })
    );
  }

  async setAvatar(url, profileKey) {
    await Promise.all(
      this.servers.map(async server => {
        // this may fail
        // but we can't create a sql table to remember to retry forever
        // I think we just silently fail for now
        await server.setAvatar(url, profileKey);
      })
    );
  }
}

// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6
module.exports = LokiPublicChatFactoryAPI;