import moduleForComponent from 'web-client/tests/helpers/module-for-component';
import { test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import FakeSearch from '../../helpers/fake-search';
import wait from 'ember-test-helpers/wait';
import waitBetween from 'web-client/tests/helpers/wait-between';
import SearchUI from 'web-client/tests/pages/search';
import run from 'ember-runloop';
import EmberObject from 'ember-object';
import Evented from 'ember-evented';

moduleForComponent('navigation-search', 'Integration | Component | navigation-search', {
  beforeEach() {
    $('#ember-testing').append("<div id='wormhole-overlays'></div>");
    this.ui = new SearchUI(this);
    this.owner.register('service:search', FakeSearch);
    this.inject.service('search');

    // Disable transitions from link clicks
    const FakeRouter = EmberObject.extend(Evented, { _doTransition() {} });
    this.owner.register('router:main', FakeRouter);
  }
});

test('toggling the search panel with text input and body click events', function(assert) {
  assert.expect(5);

  this.render(hbs`{{navigation-search}}`);

  assert.equal(this.ui.hasPanel, false, "no search-panel at first");

  this.ui.enterTerm('dummy');

  assert.equal(this.ui.hasPanel, true, "search-panel appears after entering a term");

  this.ui.input.click();

  assert.equal(this.ui.hasPanel, true, "clicking on the input does not close the panel");

  this.ui.panel.click();

  assert.equal(this.ui.hasPanel, true, "clicking on the panel does not close the panel");

  this.ui.bodyClick();

  return wait().then(() => {
    assert.equal(this.ui.hasPanel, false, "search-panel disappears");
  });
});

test('initial render with isActivated', function(assert) {
  this.render(hbs`{{navigation-search isActivated=true}}`);
  assert.equal(this.ui.isInputFocused, true, "input is focused after rendering");
});

test('showing no-results when Top Results has no results', function(assert) {
  assert.expect(2);

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('dummy');

  return wait().then(() => {
    assert.equal(this.ui.panelHeader, 'Top Results', "header starts with 'Top Results'");
    assert.equal(this.ui.panelBodyNoResults, 'No results found', 'Panel contains no results');
  });
});

test('showing no-results when filtered section has no results', function(assert) {
  assert.expect(2);
  this.get('search').setProperties({
    videos: [{ title: "video-title" }]
  });

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('video');

  return wait().then(() => {
    this.ui.gamesHeader.click();
    return wait();
  }).then(() => {
    assert.equal(this.ui.panelHeader, 'Games', "header starts with 'Games'");
    assert.equal(this.ui.panelBodyNoResults, 'No results found', 'Panel contains no results');
  });
});

test('showing loading spinner while waiting on first query', function(assert) {
  assert.expect(4);

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('dummy');

  assert.equal(this.ui.isShowingSpinner, true, "showing the spinner");

  return wait().then(() => {
    assert.equal(this.ui.isShowingSpinner, false, "stopped showing the spinner");
    assert.equal(this.ui.panelBodyNoResults, 'No results found', 'Panel contains no results');

    this.ui.enterTerm('dummy-dummy');

    assert.equal(this.ui.isShowingSpinner, false, "does not show spinner on subsequent requests");
  });
});

test('pre-rendering the search items when results return quickly', function(assert) {
  assert.expect(3);

  // Return results, but keep isWaiting state. This will happen if the query
  // results return before our manitory spinner delay time.
  this.set('request', {
    isWaiting: true,

    start(callbacks) {
      callbacks.success({
        games: {
          hits: [{ name: 'game-name' }],
          totalHitCount: 1
        }
      });
    },

    cancel() {}
  });

  this.render(hbs`{{navigation-search request=request}}`);
  this.ui.enterTerm('dummy');

  assert.equal(this.ui.isShowingSpinner, true, "showing the spinner");
  assert.equal(this.ui.panelResultsVisibility, 'hidden', "results are rendered, but hidden");
  assert.deepEqual(this.ui.searchHits, {
    games: ["game-name"],
    vods: [],
    users: [],
    channels: []
  }, "loads the game content even though the spinner is still showing");
});

test('showing correct sorting of result sections', function(assert) {
  assert.expect(1);
  this.get('search').setProperties({
    videos: [{ title: "term-video-title" }],
    channels: [{ name: "term-channel-name" }]
  });

  this.render(hbs`{{navigation-search}}`);

  this.ui.enterTerm('term');

  return wait().then(() => {
    assert.deepEqual(this.ui.titleSeparators, ['Live', 'Videos', 'Games', 'Channels']);
  });
});

test('searching for a term', function(assert) {
  assert.expect(1);

  // Only the results with term-* should show up.
  this.get('search').setProperties({
    games: [{ name: "game-name" }, { name: 'term-game-name' }],
    videos: [{ title: "video-title" }, { title: 'term-video-title' }],
    users: [{ name: "user-name" }, { name: 'term-user-name' }],
    channels: [{ name: "channel-name" }, { name: 'term-channel-name' }]
  });

  this.render(hbs`{{navigation-search}}`);

  this.ui.enterTerm('term');

  return wait().then(() => {
    assert.deepEqual(this.ui.searchHits, {
      games: ["term-game-name"],
      vods: ["term-video-title"],
      users: ["term-user-name"],
      channels: ["term-channel-name"]
    }, "only the results matching the term are shown");
  });
});

test('filtering results by clicking a section header', function(assert) {
  assert.expect(2);

  this.get('search').setProperties({
    games: [{ name: "term-game-name" }],
    videos: [{ title: "term-video-title" }],
    users: [{ name: "term-user-name" }],
    channels: [{ name: "term-channel-name" }]
  });

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('term');

  return wait().then(() => {
    this.ui.gamesHeader.click();
    return wait();
  }).then(() => {
    assert.deepEqual(this.ui.searchHits, {
      games: ["term-game-name"],
      vods: [],
      users: [],
      channels: []
    }, "only shows the game results");

    this.ui.backButton.click();
    return wait();
  }).then(() => {
    assert.deepEqual(this.ui.searchHits, {
      games: ["term-game-name"],
      vods: ["term-video-title"],
      users: ["term-user-name"],
      channels: ["term-channel-name"]
    }, "shows all results");
  });
});

test('using filter icons to remove filters', function(assert) {
  assert.expect(5);

  this.get('search').setProperties({
    videos: [{ title: "video-title" }]
  });

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('video');

  return wait().then(() => {
    assert.equal(this.ui.panelHeader, 'Top Results', "header starts with 'Top Results'");
    this.ui.gamesHeader.click();
    return wait();
  }).then(() => {
    assert.equal(this.ui.panelHeader, 'Games', "panel header shows 'Games'");
    assert.equal(this.ui.filterIcon.find('.svg-nav_games').length, 1, "showing the games filter icon");
    this.ui.filterIcon.click();
    return wait();
  }).then(() => {
    assert.equal(this.ui.panelHeader, 'Top Results', "header shows 'Top Results' again");
    assert.ok(!this.ui.hasFilterIcon, "filter games icon is gone");
  });
});

test('toggling the search panel with keyboard shortcuts', function(assert) {
  assert.expect(9);

  this.get('search').setProperties({
    videos: [{ title: "video-title" }]
  });

  let deactivateCount = 0;
  this.set('onDeactivate', () => deactivateCount++);

  let activateCount = 0;
  this.set('onActivate', () => activateCount++);

  this.render(hbs`{{navigation-search onDeactivate=onDeactivate onActivate=onActivate}}`);

  this.ui.pressAltF();

  assert.equal(this.ui.hasPanel, false, "search-panel remains closed");
  assert.equal(this.ui.isInputFocused, true, "input is focused after pressing alt+f");
  assert.equal(activateCount, 1, "activate was called");

  this.ui.enterTerm('video');
  assert.equal(this.ui.hasPanel, true, "search-panel is open");

  return wait().then(() => {
    this.ui.gamesHeader.click();

    return wait();
  }).then(() => {
    assert.equal(this.ui.panelHeader, 'Games', "game filter is set");
    this.ui.pressEsc();

    return wait();
  }).then(() => {
    assert.equal(this.ui.panelHeader, 'Top Results', "game filter is removed after pressing escape");

    assert.equal(deactivateCount, 0, "precondition - deactivate has not been called yet");
    this.ui.pressEsc();

    return wait();
  }).then(() => {
    assert.equal(this.ui.hasPanel, false, "search-panel disappears after pressing escape");
    assert.equal(deactivateCount, 1, "deactivate was called once");
  });
});

test('handling network errors', function(assert) {
  assert.expect(2);

  this.get('search').simulateNetworkErrors = true;
  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('dummy');

  return wait().then(() => {
    assert.equal(this.ui.isShowingErrorMessage, true, "shows the error message");
    this.get('search').simulateNetworkErrors = false;
    this.ui.enterTerm('fixed');
    return wait();
  }).then(() => {
    assert.equal(this.ui.isShowingErrorMessage, false, "no error message when network recovers");
  });
});

test('positioning search in the header', function(assert) {
  assert.expect(2);

  // Control scrollbar fluctuations as test runs
  document.body.style.overflow = "hidden";

  this.render(hbs`{{navigation-search isInHeader=true}}`);
  this.ui.enterTerm('dummy');

  return wait().then(() => {
    assert.ok(this.ui.panel.hasClass('search-panel--fly', "uses slide down animation"));
    assert.equal(this.ui.panel.attr('style'), `left: ${this.ui.input.offset().left}px`, "horizontally positions the panel inline with the field");
  }).finally(() => {
    document.body.style.overflow = "auto";
  });
});

test('positioning search in the sidebar', function(assert) {
  assert.expect(2);

  this.render(hbs`{{navigation-search isInHeader=false}}`);
  this.ui.enterTerm('dummy');

  return wait().then(() => {
    assert.ok(this.ui.panel.hasClass('search-panel--slide', "uses slide from left animation"));
    assert.equal(this.ui.panel.attr('style'), undefined, "does not override style");
  });
});

test('tracks an event on every query', function(assert) {
  assert.expect(2);

  // One item will match the query term and one won't
  this.get('search').setProperties({
    games: [{ name: "game-name" }],
    videos: [{ title: "term-video-title" }]
  });

  this.render(hbs`{{navigation-search}}`);
  this.ui.enterTerm('nomatch');

  return wait().then(() => {
    assert.trackEvent('search_query', { query: 'nomatch', count: 0 });
  }).then(() => {
    this.ui.enterTerm('term');
    return wait();
  }).then(() => {
    assert.trackEvent('search_query', { query: 'term', count: 1 });
  });
});

test('tracks abandoned and completed search events with term', function(assert) {
  assert.expect(20);

  this.get('search').setProperties({
    games: [{ name: "success-term-game-name" }]
  });

  this.render(hbs`{{navigation-search}}`);

  // Click away no term
  this.ui.enterTerm('dummy');
  this.ui.enterTerm('');
  this.ui.bodyClick();

  return waitBetween(() => {
    assert.trackEventCount('abandoned_search', 0);
    assert.trackEventCount('completed_search', 0);

    // Escape away no term
    this.ui.enterTerm('dummy');
    this.ui.enterTerm('');
    this.ui.pressEsc();
  }, () => {
    assert.trackEventCount('abandoned_search', 0);
    assert.trackEventCount('completed_search', 0);

    // Transition away no term
    this.ui.enterTerm('dummy');
    this.ui.enterTerm('');
    run(() => {
      this.owner.lookup('router:main').trigger('didTransition');
    });
  }, () => {
    assert.trackEventCount('abandoned_search', 0);
    assert.trackEventCount('completed_search', 0);

    // Click away with term
    this.ui.enterTerm('click-away-term');
  }, () => {
    this.ui.bodyClick();
  }, () => {
    assert.trackEvent('abandoned_search', { query: 'click-away-term' });
    assert.trackEventCount('abandoned_search', 1);
    assert.trackEventCount('completed_search', 0);

    this.ui.enterTerm('esc-term');
  }, () => {
    // Escape away with term
    this.ui.pressEsc();
  }, () => {
    assert.trackEvent('abandoned_search', { query: 'esc-term' });
    assert.trackEventCount('abandoned_search', 2);
    assert.trackEventCount('completed_search', 0);

    // Transition away with term
    this.ui.enterTerm('transition-term');
  }, () => {
    run(() => {
      this.owner.lookup('router:main').trigger('willTransition');
    });
  }, () => {
    assert.trackEvent('abandoned_search', { query: 'transition-term' });
    assert.trackEventCount('abandoned_search', 3, 'tracks transitions as abandoned search with no click-through');
    assert.trackEventCount('completed_search', 0);

    // Transition away with term after click-through
    this.ui.enterTerm('success-term');
  }, () => {
    this.ui.clickGame('success-term-game-name');
    run(() => {
      this.owner.lookup('router:main').trigger('willTransition');
    });
  }, () => {
    assert.trackEvent('completed_search', { query: 'success-term' });
    assert.trackEventCount('completed_search', 1, 'tracks completed event after click-through');
    assert.trackEventCount('abandoned_search', 3, 'does not track abandoned event after click-through');

    // User closes browser tab or navigates to another page
    this.ui.enterTerm('ragequit-term');
    $(window).trigger('beforeunload');
  }, () => {
    assert.trackEventCount('completed_search', 1, 'does not send another completed search event');
    assert.trackEventCount('abandoned_search', 4, 'sends an abandoned search event');
  });
});

test('tracks began_search events', function(assert) {
  assert.expect(5);

  this.render(hbs`{{navigation-search}}`);

  this.ui.enterTerm('dummy');

  return waitBetween(() => {
    assert.trackEventCount('began_search', 1, 'tracks began_search event when user enters a term');
    this.ui.enterTerm('dummy-dummy');
  }, () => {
    assert.trackEventCount('began_search', 1, 'tracks no more events as user continues to query');
    this.ui.enterTerm('');
  }, () => {
    assert.trackEventCount('began_search', 1, 'deleting term tracks no more events');
    this.ui.enterTerm('dummy');
  }, () => {
    assert.trackEventCount('began_search', 2, 'starting over after deleting term tracks another event');
    this.ui.bodyClick();
  }, () => {
    this.ui.pressAltF();
  }, () => {
    assert.trackEventCount('began_search', 3, 'clicking away and then returning to a field with a value tracks another event');
  });
});
