import { Meteor } from "meteor/meteor";
import { Tracker } from "meteor/tracker";
import { HTTP } from "meteor/http";
import { toSockjsUrl } from "./urls.js";
import { ClientStream } from "meteor/socket-stream-client";
import isEqual from "lodash.isequal";
import once from "lodash.once";

Tinytest.add('stream - status', function(test) {
  // Very basic test. Just see that it runs and returns something. Not a
  // lot of coverage, but enough that it would have caught a recent bug.
  var status = Meteor.status();
  test.equal(typeof status, 'object');
  test.isTrue(status.status);
});

testAsyncMulti('stream - reconnect', [
  function(test, expect) {
    var callback = once(
      expect(function() {
        var status;
        status = Meteor.status();
        test.equal(status.status, 'connected');

        Meteor.reconnect();
        status = Meteor.status();
        test.equal(status.status, 'connected');

        Meteor.reconnect({ _force: true });
        status = Meteor.status();
        test.equal(status.status, 'waiting');
      })
    );

    if (Meteor.status().status !== 'connected')
      Meteor.connection._stream.on('reset', callback);
    else callback();
  }
]);

// Disconnecting and reconnecting transitions through the correct statuses.
testAsyncMulti('stream - basic disconnect', [
  function(test, expect) {
    var history = [];
    var stream = new ClientStream('/');
    var onTestComplete = expect(function(unexpectedHistory) {
      stream.disconnect();
      if (unexpectedHistory) {
        test.fail(
          'Unexpected status history: ' + JSON.stringify(unexpectedHistory)
        );
      }
    });

    Tracker.autorun(function() {
      var status = stream.status();

      if (history[history.length -1] !== status.status) {
        history.push(status.status);

        if (isEqual(history, ['connecting'])) {
          // do nothing; wait for the next state
        } else if (isEqual(history, ['connecting', 'connected'])) {
          stream.disconnect();
        } else if (isEqual(history, ['connecting', 'connected', 'offline'])) {
          stream.reconnect();
        } else if (
          isEqual(history, [
            'connecting',
            'connected',
            'offline',
            'connecting'
          ])
        ) {
          // do nothing; wait for the next state
        } else if (
          isEqual(history, [
            'connecting',
            'connected',
            'offline',
            'connecting',
            'connected'
          ])
        ) {
          onTestComplete();
        } else if (
          _.isEqual(history, [
            'connecting',
            'connected',
            'offline',
            'connecting',
            'connected',
            'offline',
          ])
        ) {
          // do nothing;
        } else {
          onTestComplete(history);
        }
      }
    });
  }
]);

// Remain offline if the online event is received while offline.
testAsyncMulti('stream - disconnect remains offline', [
  function(test, expect) {
    var history = [];
    var stream = new ClientStream('/');
    var onTestComplete = expect(function(unexpectedHistory) {
      stream.disconnect();
      if (unexpectedHistory) {
        test.fail(
          'Unexpected status history: ' + JSON.stringify(unexpectedHistory)
        );
      }
    });

    Tracker.autorun(function() {
      var status = stream.status();

      if (history[history.length - 1] !== status.status) {
        history.push(status.status);

        if (isEqual(history, ['connecting'])) {
          // do nothing; wait for the next status
        } else if (isEqual(history, ['connecting', 'connected'])) {
          stream.disconnect();
        } else if (isEqual(history, ['connecting', 'connected', 'offline'])) {
          stream._online();
          test.isTrue(status.status === 'offline');
          onTestComplete();
        } else {
          onTestComplete(history);
        }
      }
    });
  }
]);

Tinytest.add('stream - sockjs urls are computed correctly', function(test) {
  var testHasSockjsUrl = function(raw, expectedSockjsUrl) {
    var actual = toSockjsUrl(raw);
    if (expectedSockjsUrl instanceof RegExp)
      test.isTrue(actual.match(expectedSockjsUrl), actual);
    else test.equal(actual, expectedSockjsUrl);
  };

  testHasSockjsUrl(
    'http://subdomain.meteor.com/',
    'http://subdomain.meteor.com/sockjs'
  );
  testHasSockjsUrl(
    'http://subdomain.meteor.com',
    'http://subdomain.meteor.com/sockjs'
  );
  testHasSockjsUrl(
    'subdomain.meteor.com/',
    'http://subdomain.meteor.com/sockjs'
  );
  testHasSockjsUrl(
    'subdomain.meteor.com',
    'http://subdomain.meteor.com/sockjs'
  );
  testHasSockjsUrl('/', Meteor._relativeToSiteRootUrl('/sockjs'));

  testHasSockjsUrl('http://localhost:3000/', 'http://localhost:3000/sockjs');
  testHasSockjsUrl('http://localhost:3000', 'http://localhost:3000/sockjs');
  testHasSockjsUrl('localhost:3000', 'http://localhost:3000/sockjs');

  testHasSockjsUrl(
    'https://subdomain.meteor.com/',
    'https://subdomain.meteor.com/sockjs'
  );
  testHasSockjsUrl(
    'https://subdomain.meteor.com',
    'https://subdomain.meteor.com/sockjs'
  );

  testHasSockjsUrl(
    'ddp+sockjs://ddp--****-foo.meteor.com/sockjs',
    /^https:\/\/ddp--\d\d\d\d-foo\.meteor\.com\/sockjs$/
  );
  testHasSockjsUrl(
    'ddpi+sockjs://ddp--****-foo.meteor.com/sockjs',
    /^http:\/\/ddp--\d\d\d\d-foo\.meteor\.com\/sockjs$/
  );
});

testAsyncMulti('stream - /websocket is a websocket endpoint', [
  function(test, expect) {
    //
    // Verify that /websocket and /websocket/ don't return the main page
    //
    ['/websocket', '/websocket/'].forEach((path) => {
      HTTP.get(
        Meteor._relativeToSiteRootUrl(path),
        expect(function(error, result) {
          test.isNotNull(error);
          test.equal('Not a valid websocket request', result.content);
        })
      );
    });

    //
    // For sanity, also verify that /websockets and /websockets/ return
    // the main page
    //

    // Somewhat contorted but we can't call nested expects (XXX why?)
    var pageContent;
    var wrappedCallback = expect(function(error, result) {
      test.isNull(error);
      test.equal(pageContent, result.content);
    });

    HTTP.get(
      Meteor._relativeToSiteRootUrl('/'),
      expect(function(error, result) {
        test.isNull(error);
        pageContent = result.content;

        ['/websockets', '/websockets/'].forEach(function(path) {
          HTTP.get(Meteor._relativeToSiteRootUrl(path), wrappedCallback);
        });
      })
    );
  }
]);