/*
 * NOTICE:  This file is owned by the chat and messaging team (#chat-and-messaging on slack)
 * If you make changes to this file or any of its chat-effecting dependencies, you must do the following two things:
 * 1)  Test chat and whispers in a staging environment
 * 2)  Get a PR approved by a member of the chat and messaging team
 *
 * Thanks!
 */

import Ember from 'ember';
import urlParams from '../utilities/url-params';
import { assert } from 'ember-metal/utils';

const { RSVP, Evented, $, computed, Service, on, merge, Logger, inject } = Ember;

let imHostport = 'im.twitch.tv',
    forceHost = urlParams.im_host,
    forcePort = parseInt(urlParams.im_port);

if (forceHost) {
  let HOST_WHITELIST = [/^localhost$/, /\.twitch\.tv$/];

  let rejected = true;
  for (let index in HOST_WHITELIST) {
    if (HOST_WHITELIST[index].test(forceHost)) {
      rejected = false;
      imHostport = forceHost;
      if (forcePort) {
        imHostport += `:${forcePort}`;
      }
      break;
    }
  }

  if (rejected) {
    let error = "Non-whitelisted im_host";
    Logger.error(error);
    throw error;
  }
}

const RECEIVER_URL = '/static/receiver.html',
      BASE_URL = `${window.location.protocol}//${imHostport}`;

export default Service.extend(Evented, {
  session: inject.service(),
  ajax: inject.service(),

  setupIframe: on('init', function () {
    let promise = new RSVP.Promise(resolve => {
      this.on('iframeIsReady', () => resolve());
    });

    this.set('_iframeReadyPromise', promise);

    let $iframe = $('<iframe>').attr('src', BASE_URL + RECEIVER_URL);
    $iframe = $iframe.appendTo('head').get(0);
    this.set('iframe', $iframe);

    document.domain = 'twitch.tv';
  }),

  willDestroy() {
    this.get('iframe').remove();
    this._super(...arguments);
  },

  publishMessage(message, options) {
    if (!options.toLogin && !options.toId) {
      throw new Error("Missing toLogin or toId");
    }

    if (options.toLogin && options.toId) {
      throw new Error("Cannot have both toLogin and toId");
    }

    return this.get('session').getCurrentUser().then((userData) => {
      if (this.isDestroyed) { return; }
      let payload = {
        body: message,
        from_id: userData.id
      };

      if (options.toLogin) {
        payload.to_login = options.toLogin;
      }

      if (options.toId) {
        payload.to_id = options.toId;
      }

      payload.nonce = options.nonce;

      return this.makeRequest('POST', "/v1/messages", payload);
    });

  },

  loadMessages(query) {
    assert(`Missing query`, !!query);
    assert(`Missing query.threadId`, !!query.threadId);
    assert(`Missing query.queryParams`, !!query.queryParams);

    let path = `/v1/threads/${query.threadId}/messages`;
    return this.makeRequest('GET', path, query.queryParams).then(response => {
      let rawMessages = response.data.sort((a,b) => a.id - b.id);
      return rawMessages;
    });
  },

  // FIXME: Not sure if the promise always resolves if im-api service requests never return.
  // Add a timeout to the ajax request.
  makeRequest(method, path, data, opts) {
    return this._constructOptions(method, path, data, opts).then(options => {
      if (this.isDestroyed || this.isDestroying) { return; }
      return this.get('ajax').raw(options.url, options).then(({ response }) => {
        if (this.isDestroyed || this.isDestroying) { return; }

        // JSONAPI expects the self url
        response.links = {
          self: options.url
        };

        return response;
      }, (error) => {
        if (this.isDestroyed || this.isDestroying) { return; }

        throw error;
      });
    });
  },

  // By default use XMLHttpRequest from iframe.
  // Create this property to allow overriding for test stubbing.
  xhrConstructor: computed('iframe', function() {
    return this.get('iframe').contentWindow.XMLHttpRequest;
  }),

  _constructOptions(method, path, data, options) {
    let defaultData = {};
    defaultData.on_site = ''; // IMStore expects an empty string for the on_site param

    return RSVP.Promise.all([
      this.get('session').getCurrentUser(),
      this.get('_iframeReadyPromise')
    ]).then((values) => {
      if (this.isDestroyed) { return; }

      let [userData] = values;
      let token = userData.chat_oauth_token;

      options = merge({}, options);
      options.headers = options.headers || {};

      options.data = $.extend(defaultData, data, options.data);

      if (path.substr(0, 1) === '/') {
        path = `${BASE_URL}${path}`;
      }

      if (method !== 'GET') {
        try {
          options.data = JSON.stringify(options.data);
          path = `${path}?on_site=`; // IMStore expects the on_site value as a query param, even for non GET requests
        } catch (e) {
          throw new Error('invalid JSON for request');
        }
      }

      options.headers.Authorization = `OAuth ${token}`;
      options.type = options.type || method;

      // Note: If we ever need to change the dataType to 'jsonp', we also have to manually
      // trigger the ajaxSend and ajaxComplete events. These are required to make the tests
      // wait for ajax requests to finish. See app/services/api.js
      merge(options, {
        url: path,
        dataType: 'json',
        contentType: 'application/json',
        cache: true,
        global: false,
        retryNum: 0
      });

      options.xhr = () => {
        let XhrConstructor = this.get('xhrConstructor');
        return new XhrConstructor();
      };
      options.beforeSend = (jqXHR, settings) => {
        settings.crossDomain = false;
      };
      return options;
    });
  }
});
