import Controller from '@ember/controller';
import { capitalize } from '@ember/string';
import { Route } from '@ember/-internals/routing';
import { get } from '@ember/-internals/metal';
import { RouterTestCase, moduleFor } from 'internal-test-helpers';

function setupController(app, name) {
  let controllerName = `${capitalize(name)}Controller`;

  Object.defineProperty(app, controllerName, {
    get() {
      throw new Error(`Generating a URL should not require instantiation of a ${controllerName}.`);
    },
  });
}

moduleFor(
  'Router Service - urlFor',
  class extends RouterTestCase {
    ['@test RouterService#urlFor returns URL for simple route'](assert) {
      assert.expect(1);

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child');

        assert.equal('/child', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with dynamic segments'](assert) {
      assert.expect(1);

      setupController(this.application, 'dynamic');

      let dynamicModel = { id: 1, contents: 'much dynamicism' };

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('dynamic', dynamicModel);

        assert.equal('/dynamic/1', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with basic query params'](assert) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: 'bar' });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child', queryParams);

        assert.equal('/child?foo=bar', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with basic query params and default value'](
      assert
    ) {
      assert.expect(1);

      this.add(
        'controller:parent.child',
        Controller.extend({
          queryParams: ['sort'],
          sort: 'ASC',
        })
      );

      let queryParams = this.buildQueryParams({ sort: 'ASC' });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child', queryParams);

        assert.equal('/child?sort=ASC', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with basic query params and default value with stickyness'](
      assert
    ) {
      assert.expect(2);

      this.add(
        'controller:parent.child',
        Controller.extend({
          queryParams: ['sort', 'foo'],
          sort: 'ASC',
        })
      );

      return this.visit('/child/?sort=DESC').then(() => {
        let controller = this.applicationInstance.lookup('controller:parent.child');
        assert.equal(get(controller, 'sort'), 'DESC', 'sticky is set');

        let queryParams = this.buildQueryParams({ foo: 'derp' });
        let actual = this.routerService.urlFor('parent.child', queryParams);

        assert.equal(actual, '/child?foo=derp', 'does not use "stickiness"');
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with array as query params'](assert) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({
        selectedItems: ['a', 'b', 'c'],
      });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child', queryParams);

        assert.equal('/child?selectedItems[]=a&selectedItems[]=b&selectedItems[]=c', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with null query params'](assert) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: null });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child', queryParams);

        assert.equal('/child', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with undefined query params'](
      assert
    ) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: undefined });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('parent.child', queryParams);

        assert.equal('/child', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with dynamic segments and basic query params'](
      assert
    ) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: 'bar' });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('dynamic', { id: 1 }, queryParams);

        assert.equal('/dynamic/1?foo=bar', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with dynamic segments and array as query params'](
      assert
    ) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({
        selectedItems: ['a', 'b', 'c'],
      });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('dynamic', { id: 1 }, queryParams);

        assert.equal(
          '/dynamic/1?selectedItems[]=a&selectedItems[]=b&selectedItems[]=c',
          expectedURL
        );
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with dynamic segments and null query params'](
      assert
    ) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: null });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('dynamic', { id: 1 }, queryParams);

        assert.equal('/dynamic/1', expectedURL);
      });
    }

    ['@test RouterService#urlFor returns URL for simple route with dynamic segments and undefined query params'](
      assert
    ) {
      assert.expect(1);

      let queryParams = this.buildQueryParams({ foo: undefined });

      return this.visit('/').then(() => {
        let expectedURL = this.routerService.urlFor('dynamic', { id: 1 }, queryParams);

        assert.equal('/dynamic/1', expectedURL);
      });
    }

    ['@test RouterService#urlFor correctly transitions to route via generated path'](assert) {
      assert.expect(1);

      let expectedURL;

      return this.visit('/')
        .then(() => {
          expectedURL = this.routerService.urlFor('parent.child');

          return this.routerService.transitionTo(expectedURL);
        })
        .then(() => {
          assert.equal(expectedURL, this.routerService.get('currentURL'));
        });
    }

    ['@test RouterService#urlFor correctly transitions to route via generated path with dynamic segments'](
      assert
    ) {
      assert.expect(1);

      let expectedURL;
      let dynamicModel = { id: 1 };

      this.add(
        'route:dynamic',
        Route.extend({
          model() {
            return dynamicModel;
          },
        })
      );

      return this.visit('/')
        .then(() => {
          expectedURL = this.routerService.urlFor('dynamic', dynamicModel);

          return this.routerService.transitionTo(expectedURL);
        })
        .then(() => {
          assert.equal(expectedURL, this.routerService.get('currentURL'));
        });
    }

    ['@test RouterService#urlFor correctly transitions to route via generated path with query params'](
      assert
    ) {
      assert.expect(1);

      let expectedURL;
      let actualURL;
      let queryParams = this.buildQueryParams({ foo: 'bar' });

      return this.visit('/')
        .then(() => {
          expectedURL = this.routerService.urlFor('parent.child', queryParams);

          return this.routerService.transitionTo(expectedURL);
        })
        .then(() => {
          actualURL = `${this.routerService.get('currentURL')}?foo=bar`;

          assert.equal(expectedURL, actualURL);
        });
    }

    ['@test RouterService#urlFor correctly transitions to route via generated path with dynamic segments and query params'](
      assert
    ) {
      assert.expect(1);

      let expectedURL;
      let actualURL;
      let queryParams = this.buildQueryParams({ foo: 'bar' });
      let dynamicModel = { id: 1 };

      this.add(
        'route:dynamic',
        Route.extend({
          model() {
            return dynamicModel;
          },
        })
      );

      return this.visit('/')
        .then(() => {
          expectedURL = this.routerService.urlFor('dynamic', dynamicModel, queryParams);

          return this.routerService.transitionTo(expectedURL);
        })
        .then(() => {
          actualURL = `${this.routerService.get('currentURL')}?foo=bar`;

          assert.equal(expectedURL, actualURL);
        });
    }
  }
);