var QUnit = require('qunitjs');
var RSVP = require('rsvp');
var SimpleDOM = require('simple-dom');
var appModule = require('./helpers/app-module');

function assertHTMLMatches(assert, actualHTML, expectedHTML) {
  assert.ok(actualHTML.match(expectedHTML), actualHTML + " matches " + expectedHTML);
}

function handleError(assert) {
  return function(error) {
    assert.ok(false, error.stack);
  };
}

// This is based on what fastboot-server does
var HTMLSerializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap);

function fastbootVisit(App, url) {
  var doc = new SimpleDOM.Document();
  var rootElement = doc.body;
  var options = { isBrowser: false, document: doc, rootElement: rootElement };

  return App.visit(url, options).then(function(instance) {
    try {
      return {
        url: instance.getURL(),
        title: doc.title,
        body: HTMLSerializer.serialize(rootElement)
      };
    } finally {
      instance.destroy();
    }
  });
}

function assertFastbootResult(assert, expected) {
  return function(actual) {
    assert.equal(actual.url, expected.url);
    assertHTMLMatches(assert, actual.body, expected.body);
  };
}

appModule("Ember.Application - visit() Integration Tests");

// Because running these tests relies on certain feature flags being
// enabled, we check to see if those flags are on before registering
// the tests. Once all feature flags are on by default we can remove
// this conditional.
if (appModule.canRunTests) {
  QUnit.test('FastBoot: basic', function(assert) {
    this.routes(function() {
      this.route('a');
      this.route('b');
    });

    this.template('application', '<h1>Hello world</h1>\n{{outlet}}');
    this.template('a', '<h2>Welcome to {{x-foo page="A"}}</h2>');
    this.template('b', '<h2>{{x-foo page="B"}}</h2>');
    this.template('components/x-foo', 'Page {{page}}');

    var initCalled = false;
    var didInsertElementCalled = false;

    this.component('x-foo', {
      tagName: 'span',
      init: function() {
        this._super();
        initCalled = true;
      },
      didInsertElement: function() {
        didInsertElementCalled = true;
      }
    });

    var App = this.createApplication();

    return RSVP.all([
      fastbootVisit(App, '/a').then(
        assertFastbootResult(assert, {
          url: '/a',
          body: '<h1>Hello world</h1>\n<h2>Welcome to <span id=".+" class="ember-view">Page A</span></h2>'
        }),
        handleError(assert)
      ),
      fastbootVisit(App, '/b').then(
        assertFastbootResult(assert, {
          url: '/b',
          body: '<h1>Hello world</h1>\n<h2><span id=".+" class="ember-view">Page B</span></h2>'
        }),
        handleError
      )
    ]).then(function() {
      assert.ok(initCalled, 'Component#init should be called');
      assert.ok(!didInsertElementCalled, 'Component#didInsertElement should not be called');
    });
  });

  QUnit.test('FastBoot: redirect', function(assert) {
    this.routes(function() {
      this.route('a');
      this.route('b');
      this.route('c');
    });

    this.template('a', '<h1>Hello from A</h1>');
    this.template('b', '<h1>Hello from B</h1>');
    this.template('c', '<h1>Hello from C</h1>');

    this.route('a', {
      beforeModel: function() {
        this.replaceWith('b');
      }
    });

    this.route('b', {
      afterModel: function() {
        this.transitionTo('c');
      }
    });

    var App = this.createApplication();

    return RSVP.all([
      fastbootVisit(App, '/a').then(
        assertFastbootResult(assert, { url: '/c', body: '<h1>Hello from C</h1>' }),
        handleError(assert)
      ),
      fastbootVisit(App, '/b').then(
        assertFastbootResult(assert, { url: '/c', body: '<h1>Hello from C</h1>' }),
        handleError(assert)
      )
    ]);
  });

  QUnit.test('FastBoot: attributes are sanitized', function(assert) {
    this.template('application', '<a href={{test}}></a>');

    this.controller('application', {
      /*jshint scripturl:true*/
      test: 'javascript:alert("hello")'
    });

    var App = this.createApplication();

    return RSVP.all([
      fastbootVisit(App, '/').then(
        assertFastbootResult(assert, { url: '/', body: '<a href="unsafe:javascript:alert\\(&quot;hello&quot;\\)"></a>' }),
        handleError(assert)
      )
    ]);
  });

  QUnit.test('FastBoot: route error', function(assert) {
    this.routes(function() {
      this.route('a');
      this.route('b');
    });

    this.template('a', '<h1>Hello from A</h1>');
    this.template('b', '<h1>Hello from B</h1>');

    this.route('a', {
      beforeModel: function() {
        throw new Error('Error from A');
      }
    });

    this.route('b', {
      afterModel: function() {
        throw new Error('Error from B');
      }
    });

    var App = this.createApplication();

    return RSVP.all([
      fastbootVisit(App, '/a').then(
        function(instance) {
          assert.ok(false, 'It should not render');
          instance.destroy();
        },
        function(error) {
          assert.equal(error.message, 'Error from A');
        }
      ),
      fastbootVisit(App, '/b').then(
        function(instance) {
          assert.ok(false, 'It should not render');
          instance.destroy();
        },
        function(error) {
          assert.equal(error.message, 'Error from B');
        }
      )
    ]);
  });

  QUnit.test('Resource-discovery setup', function(assert) {
    this.service('network', {
      init: function() {
        this.set('requests', []);
      },

      fetch: function(url) {
        this.get('requests').push(url);
        return RSVP.resolve();
      }
    });

    this.routes(function() {
      this.route('a');
      this.route('b');
      this.route('c');
      this.route('d');
      this.route('e');
    });

    this.route('a', {
      model: function() {
        return this.network.fetch('/a');
      },
      afterModel: function() {
        this.replaceWith('b');
      }
    });

    this.route('b', {
      model: function() {
        return this.network.fetch('/b');
      },
      afterModel: function() {
        this.replaceWith('c');
      }
    });

    this.route('c', {
      model: function() {
        return this.network.fetch('/c');
      }
    });

    this.route('d', {
      model: function() {
        return this.network.fetch('/d');
      },
      afterModel: function() {
        this.replaceWith('e');
      }
    });

    this.route('e', {
      model: function() {
        return this.network.fetch('/e');
      }
    });

    this.template('a', '{{x-foo}}');
    this.template('b', '{{x-foo}}');
    this.template('c', '{{x-foo}}');
    this.template('d', '{{x-foo}}');
    this.template('e', '{{x-foo}}');

    var xFooInstances = 0;

    this.component('x-foo', {
      init: function() {
        this._super();
        xFooInstances++;
      }
    });

    var App = this.createApplication();

    App.inject('route', 'network', 'service:network');

    function assertResources(url, resources) {
      return App.visit(url, { isBrowser: false, shouldRender: false }).then(
        function(instance) {
          try {
            var viewRegistry = instance.lookup('-view-registry:main');
            assert.strictEqual(Object.keys(viewRegistry).length, 0, 'did not create any views');

            var networkService = instance.lookup('service:network');
            assert.deepEqual(networkService.get('requests'), resources);
          } finally {
            instance.destroy();
          }
        },
        handleError(assert)
      );
    }

    return RSVP.all([
      assertResources('/a', ['/a', '/b', '/c']),
      assertResources('/b', ['/b', '/c']),
      assertResources('/c', ['/c']),
      assertResources('/d', ['/d', '/e']),
      assertResources('/e', ['/e'])
    ]).then(function() {
      assert.strictEqual(xFooInstances, 0, 'it should not create any x-foo components');
    });
  });
}
