/* global log, window, process, URL */
const EventEmitter = require('events');
const nodeFetch = require('node-fetch');
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js');

class LokiPublicChatFactoryAPI extends EventEmitter {
  constructor(ourKey) {
    super();
    this.ourKey = ourKey;
    this.servers = [];
    this.allMembers = [];
    // 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()));
  }

  static async validServer(serverUrl) {
    // test to make sure it's online (and maybe has a valid SSL cert)
    try {
      const url = new URL(serverUrl);
      // 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';
      // FIXME: use proxy when we have open groups that work with proxy
      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(`failing to created ${serverUrl}`, e.code, e.message);
      // bail out if not valid enough
      return false;
    }
    return true;
  }

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

      if (!await this.constructor.validServer(serverUrl)) {
        return null;
      }

      // after verification then we can start up all the pollers
      if (process.env.USE_STUBBED_NETWORK) {
        thisServer = new StubAppDotNetAPI(this.ourKey, serverUrl);
      } else {
        thisServer = new LokiAppDotNetAPI(this.ourKey, serverUrl);
      }

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

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

  static async getServerTime() {
    const url = `${window.getDefaultFileServer()}/loki/v1/time`;
    let timestamp = NaN;

    try {
      const res = await nodeFetch(url);
      if (res.ok) {
        timestamp = await res.text();
      }
    } catch (e) {
      return timestamp;
    }

    return Number(timestamp);
  }

  static async getTimeDifferential() {
    // Get time differential between server and client in seconds
    const serverTime = await this.getServerTime();
    const clientTime = Math.ceil(Date.now() / 1000);

    if (Number.isNaN(serverTime)) {
      return 0;
    }
    return serverTime - clientTime;
  }

  static async setClockParams() {
    // Set server-client time difference
    const maxTimeDifferential = 30;
    const timeDifferential = await this.getTimeDifferential();

    window.clientClockSynced = Math.abs(timeDifferential) < maxTimeDifferential;
    return window.clientClockSynced;
  }

  // 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);
      })
    );
  }
}

module.exports = LokiPublicChatFactoryAPI;
