import { setOwner } from '@ember/-internals/owner';
import EmberRouter from '../../lib/system/router';
import { buildOwner, moduleFor, AbstractTestCase } from 'internal-test-helpers';

let Router;

moduleFor(
  'Ember Router DSL',
  class extends AbstractTestCase {
    constructor() {
      super();
      Router = EmberRouter.extend();
    }

    teardown() {
      Router = null;
    }

    ['@test should fail when using a reserved route name'](assert) {
      let reservedNames = ['basic', 'application'];

      assert.expect(reservedNames.length);

      reservedNames.forEach(reservedName => {
        expectAssertion(() => {
          Router = EmberRouter.extend();

          Router.map(function() {
            this.route(reservedName);
          });

          let router = Router.create();
          router._initRouterJs();
        }, "'" + reservedName + "' cannot be used as a route name.");
      });
    }

    ['@test [GH#16642] better error when using a colon in a route name']() {
      expectAssertion(() => {
        Router = EmberRouter.extend();

        Router.map(function() {
          this.route('resource/:id');
        });

        let router = Router.create();
        router._initRouterJs();
      }, "'resource/:id' is not a valid route name. It cannot contain a ':'. You may want to use the 'path' option instead.");
    }

    ['@test should retain resource namespace if nested with routes'](assert) {
      Router = Router.map(function() {
        this.route('bleep', function() {
          this.route('bloop', function() {
            this.route('blork');
          });
        });
      });

      let router = Router.create();
      router._initRouterJs();

      assert.ok(
        router._routerMicrolib.recognizer.names['bleep'],
        'parent name was used as base of nested routes'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep.bloop'],
        'parent name was used as base of nested routes'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep.bloop.blork'],
        'parent name was used as base of nested routes'
      );
    }

    ['@test should add loading and error routes if _isRouterMapResult is true'](assert) {
      Router.map(function() {
        this.route('blork');
      });

      let router = Router.create({
        _hasModuleBasedResolver() {
          return true;
        },
      });

      router._initRouterJs();

      assert.ok(router._routerMicrolib.recognizer.names['blork'], 'main route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['blork_loading'],
        'loading route was added'
      );
      assert.ok(router._routerMicrolib.recognizer.names['blork_error'], 'error route was added');
    }

    ['@test should not add loading and error routes if _isRouterMapResult is false'](assert) {
      Router.map(function() {
        this.route('blork');
      });

      let router = Router.create();
      router._initRouterJs(false);

      assert.ok(router._routerMicrolib.recognizer.names['blork'], 'main route was created');
      assert.ok(
        !router._routerMicrolib.recognizer.names['blork_loading'],
        'loading route was not added'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['blork_error'],
        'error route was not added'
      );
    }

    ['@test should reset namespace of loading and error routes for routes with resetNamespace'](
      assert
    ) {
      Router.map(function() {
        this.route('blork', function() {
          this.route('blorp');
          this.route('bleep', { resetNamespace: true });
        });
      });

      let router = Router.create({
        _hasModuleBasedResolver() {
          return true;
        },
      });

      router._initRouterJs();

      assert.ok(router._routerMicrolib.recognizer.names['blork.blorp'], 'nested route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['blork.blorp_loading'],
        'nested loading route was added'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['blork.blorp_error'],
        'nested error route was added'
      );

      assert.ok(router._routerMicrolib.recognizer.names['bleep'], 'reset route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep_loading'],
        'reset loading route was added'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep_error'],
        'reset error route was added'
      );

      assert.ok(
        !router._routerMicrolib.recognizer.names['blork.bleep'],
        'nested reset route was not created'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['blork.bleep_loading'],
        'nested reset loading route was not added'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['blork.bleep_error'],
        'nested reset error route was not added'
      );
    }

    ['@test should throw an error when defining a route serializer outside an engine'](assert) {
      Router.map(function() {
        assert.throws(() => {
          this.route('posts', { serialize: function() {} });
        }, /Defining a route serializer on route 'posts' outside an Engine is not allowed/);
      });

      Router.create()._initRouterJs();
    }
  }
);

moduleFor(
  'Ember Router DSL with engines',
  class extends AbstractTestCase {
    constructor() {
      super();
      Router = EmberRouter.extend();
    }

    teardown() {
      Router = null;
    }

    ['@test should allow mounting of engines'](assert) {
      assert.expect(3);

      Router = Router.map(function() {
        this.route('bleep', function() {
          this.route('bloop', function() {
            this.mount('chat');
          });
        });
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create();
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.ok(
        router._routerMicrolib.recognizer.names['bleep'],
        'parent name was used as base of nested routes'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep.bloop'],
        'parent name was used as base of nested routes'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['bleep.bloop.chat'],
        'parent name was used as base of mounted engine'
      );
    }

    ['@test should allow mounting of engines at a custom path'](assert) {
      assert.expect(1);

      Router = Router.map(function() {
        this.route('bleep', function() {
          this.route('bloop', function() {
            this.mount('chat', { path: 'custom-chat' });
          });
        });
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create();
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.deepEqual(
        router._routerMicrolib.recognizer.names['bleep.bloop.chat'].segments
          .slice(1, 4)
          .map(s => s.value),
        ['bleep', 'bloop', 'custom-chat'],
        'segments are properly associated with mounted engine'
      );
    }

    ['@test should allow aliasing of engine names with `as`'](assert) {
      assert.expect(1);

      Router = Router.map(function() {
        this.route('bleep', function() {
          this.route('bloop', function() {
            this.mount('chat', { as: 'blork' });
          });
        });
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create();
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.deepEqual(
        router._routerMicrolib.recognizer.names['bleep.bloop.blork'].segments
          .slice(1, 4)
          .map(s => s.value),
        ['bleep', 'bloop', 'blork'],
        'segments are properly associated with mounted engine with aliased name'
      );
    }

    ['@test should add loading and error routes to a mount if _isRouterMapResult is true'](assert) {
      Router.map(function() {
        this.mount('chat');
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create({
        _hasModuleBasedResolver() {
          return true;
        },
      });
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.ok(router._routerMicrolib.recognizer.names['chat'], 'main route was created');
      assert.ok(router._routerMicrolib.recognizer.names['chat_loading'], 'loading route was added');
      assert.ok(router._routerMicrolib.recognizer.names['chat_error'], 'error route was added');
    }

    ['@test should add loading and error routes to a mount alias if _isRouterMapResult is true'](
      assert
    ) {
      Router.map(function() {
        this.mount('chat', { as: 'shoutbox' });
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create({
        _hasModuleBasedResolver() {
          return true;
        },
      });
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.ok(router._routerMicrolib.recognizer.names['shoutbox'], 'main route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['shoutbox_loading'],
        'loading route was added'
      );
      assert.ok(router._routerMicrolib.recognizer.names['shoutbox_error'], 'error route was added');
    }

    ['@test should not add loading and error routes to a mount if _isRouterMapResult is false'](
      assert
    ) {
      Router.map(function() {
        this.mount('chat');
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create();
      setOwner(router, engineInstance);
      router._initRouterJs(false);

      assert.ok(router._routerMicrolib.recognizer.names['chat'], 'main route was created');
      assert.ok(
        !router._routerMicrolib.recognizer.names['chat_loading'],
        'loading route was not added'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['chat_error'],
        'error route was not added'
      );
    }

    ['@test should reset namespace of loading and error routes for mounts with resetNamespace'](
      assert
    ) {
      Router.map(function() {
        this.route('news', function() {
          this.mount('chat');
          this.mount('blog', { resetNamespace: true });
        });
      });

      let engineInstance = buildOwner({
        ownerOptions: { routable: true },
      });

      let router = Router.create({
        _hasModuleBasedResolver() {
          return true;
        },
      });
      setOwner(router, engineInstance);
      router._initRouterJs();

      assert.ok(router._routerMicrolib.recognizer.names['news.chat'], 'nested route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['news.chat_loading'],
        'nested loading route was added'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['news.chat_error'],
        'nested error route was added'
      );

      assert.ok(router._routerMicrolib.recognizer.names['blog'], 'reset route was created');
      assert.ok(
        router._routerMicrolib.recognizer.names['blog_loading'],
        'reset loading route was added'
      );
      assert.ok(
        router._routerMicrolib.recognizer.names['blog_error'],
        'reset error route was added'
      );

      assert.ok(
        !router._routerMicrolib.recognizer.names['news.blog'],
        'nested reset route was not created'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['news.blog_loading'],
        'nested reset loading route was not added'
      );
      assert.ok(
        !router._routerMicrolib.recognizer.names['news.blog_error'],
        'nested reset error route was not added'
      );
    }
  }
);