import { TestBed } from '@angular/core/testing';
import { RouterHandler } from './router-handler.service';

import { Routes, RouterModule, Router } from '@angular/router';
import { Component } from '@angular/core';

describe('RouterHandler', () => {
  let routerHandlerService: RouterHandler;

  @Component({})
  class Comp {}

  const routes: Routes = [
    { path: 'welcome', component: Comp },
    { path: 'test', component: Comp },
    { path: 'test2/:param', component: Comp },
    { path: 'test2/:param1/:param2', component: Comp },
    { path: 'test2/:param1/:param2/test/:param3', component: Comp },
    {
      path: 'page',
      component: Comp,
      children: [
        { path: 'pagechild1', component: Comp },
        { path: 'pagechild2', component: Comp },
      ],
    },
    {
      path: 'longparent',
      component: Comp,
      children: [
        {
          path: 'firstchild1',
          component: Comp,
          children: [
            { path: 'secondchild1', component: Comp },
            { path: 'secondchild2/:param/sub', component: Comp },
          ],
        },
        { path: 'firstchild2/:param', component: Comp },
      ],
    },
    { path: '', component: Comp },
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        {
          provide: RouterHandler,
          useClass: RouterHandler,
          deps: [Router],
        },
      ],
      imports: [RouterModule.forRoot(routes, {})],
    });

    routerHandlerService = TestBed.inject(RouterHandler);
  });

  // test all used function (setItem, getItem, clear)
  it('should generate the service', () => {
    expect(routerHandlerService).toBeTruthy();
  });

  it('should make flat config without child', () => {
    let conf = [{ path: 'page1' }, { path: 'page1/:param' }, { path: 'page2' }];
    let arr = ['page1', 'page1/:param', 'page2'];

    expect(
      JSON.stringify(routerHandlerService['parseRouteConfig'](conf)) == JSON.stringify(arr)
    ).toBeTrue();
  });

  it('should make flat config with 1 level child', () => {
    let conf = [
      { path: 'page1' },
      { path: 'page2', children: [{ path: 'child1' }, { path: 'child2' }] },
      { path: 'page3', children: [{ path: 'child3' }, { path: 'child4' }] },
    ];
    let arr = [
      'page1',
      'page2',
      'page2/child1',
      'page2/child2',
      'page3',
      'page3/child3',
      'page3/child4',
    ];

    expect(
      JSON.stringify(routerHandlerService['parseRouteConfig'](conf)) == JSON.stringify(arr)
    ).toBeTrue();
  });

  it('should make flat config with X level child', () => {
    let conf = [
      { path: 'page1' },
      { path: 'page2', children: [{ path: 'child1' }, { path: 'child2' }] },
      {
        path: 'page3',
        children: [
          { path: 'child3' },
          {
            path: 'child4',
            children: [
              {
                path: 'child5',
                children: [
                  {
                    path: 'child6',
                    children: [
                      {
                        path: 'child7',
                        children: [
                          { path: 'child8' },
                          { path: 'child9', children: [{ path: 'child10' }] },
                        ],
                      },
                    ],
                  },
                  {
                    path: 'child11',
                    children: [{ path: 'child12' }, { path: 'child13' }, { path: 'child14' }],
                  },
                ],
              },
            ],
          },
        ],
      },
    ];
    let arr = [
      'page1',
      'page2',
      'page2/child1',
      'page2/child2',
      'page3',
      'page3/child3',
      'page3/child4',
      'page3/child4/child5',
      'page3/child4/child5/child6',
      'page3/child4/child5/child6/child7',
      'page3/child4/child5/child6/child7/child8',
      'page3/child4/child5/child6/child7/child9',
      'page3/child4/child5/child6/child7/child9/child10',
      'page3/child4/child5/child11',
      'page3/child4/child5/child11/child12',
      'page3/child4/child5/child11/child13',
      'page3/child4/child5/child11/child14',
    ];

    expect(
      JSON.stringify(routerHandlerService['parseRouteConfig'](conf)) == JSON.stringify(arr)
    ).toBeTrue();
  });

  it('should make correct wildcard', (done) => {
    expect(routerHandlerService['makePathWildcardFromFlatConfig']('page') == 'page').toBeTrue();
    expect(routerHandlerService['makePathWildcardFromFlatConfig']('') == '').toBeTrue();
    expect(
      routerHandlerService['makePathWildcardFromFlatConfig']('page/page2') == 'page/page2'
    ).toBeTrue();
    expect(
      routerHandlerService['makePathWildcardFromFlatConfig']('page/:param') == 'page/?'
    ).toBeTrue();
    expect(
      routerHandlerService['makePathWildcardFromFlatConfig']('page/:param/page2') == 'page/?/page2'
    ).toBeTrue();
    expect(
      routerHandlerService['makePathWildcardFromFlatConfig']('page/:param/page2/:param2') ==
        'page/?/page2/?'
    ).toBeTrue();
    expect(
      routerHandlerService['makePathWildcardFromFlatConfig'](
        'page/:param/page2/:param2/:param3/:param4/page3/:param5'
      ) == 'page/?/page2/?/?/?/page3/?'
    ).toBeTrue();
    done();
  });

  it('should detect correct params', (done) => {
    let test = [
      {
        url: 'page',
        params: [],
      },
      {
        url: 'page/:param',
        params: [{ param: 'param', pos: 1 }],
      },
      {
        url: 'page/:param1/:param2',
        params: [
          { param: 'param1', pos: 1 },
          { param: 'param2', pos: 2 },
        ],
      },
      {
        url: ':param1/page/:param2',
        params: [
          { param: 'param1', pos: 0 },
          { param: 'param2', pos: 2 },
        ],
      },
      {
        url: 'page/:param1/:param2/page2/:param3/page4/:param4',
        params: [
          { param: 'param1', pos: 1 },
          { param: 'param2', pos: 2 },
          { param: 'param3', pos: 4 },
          { param: 'param4', pos: 6 },
        ],
      },
    ];

    test.forEach((t) => {
      expect(
        JSON.stringify(routerHandlerService['detectParamsFromFlatConfig'](t.url)) ==
          JSON.stringify(t.params)
      ).toBeTrue();
    });

    done();
  });

  it('should isUrlFit work', (done) => {
    let posTest = [
      { url: 'page', wildcard: 'page' },
      { url: 'page/param', wildcard: 'page/?' },
      { url: 'page/param/subpage', wildcard: 'page/?/subpage' },
      { url: 'page/param/subpage/param2', wildcard: 'page/?/subpage/?' },
      { url: 'page/param/subpage/param2', wildcard: '?/?/?/?' },
      {
        url: 'page/param/subpage/param2',
        wildcard: 'page/param/subpage/param2',
      },
      { url: 'x/y/z', wildcard: 'x/y/z' },
      { url: 'x/y/z', wildcard: '?/?/?' },
      { url: 'x/y/z', wildcard: 'x/?/?' },
      { url: 'x/y/z', wildcard: '?/?/z' },
      { url: 'x/y/z', wildcard: '?/y/z' },
    ];

    let negTest = [
      { url: 'page', wildcard: 'nonpage' },
      { url: 'page', wildcard: '' },
      { url: 'page', wildcard: 'page/?' },
      { url: 'page', wildcard: 'page/subpage' },
      { url: 'page/param', wildcard: 'page' },
      { url: 'page/param/param2', wildcard: 'page/?/param' },
      { url: 'page/param/param2', wildcard: 'page/?/?/param' },
    ];

    posTest.forEach((t) => {
      expect(routerHandlerService['isUrlFit'](t.url, t.wildcard)).toBeTrue();
    });

    negTest.forEach((t) => {
      expect(routerHandlerService['isUrlFit'](t.url, t.wildcard)).toBeFalse();
    });

    done();
  });

  it('should isSubscriptionPatternFit work', (done) => {
    let posTest = [
      { url: 'page', wildcard: 'page' },
      { url: 'page/param', wildcard: 'page/?' },
      { url: 'page/param/subpage', wildcard: 'page/?/subpage' },
      { url: 'page/param/subpage/param2', wildcard: 'page/?/subpage/?' },
      { url: 'page/param/subpage/param2', wildcard: '?/?/?/?' },
      {
        url: 'page/param/subpage/param2',
        wildcard: 'page/param/subpage/param2',
      },
      { url: 'x/y/z', wildcard: 'x/y/z' },
      { url: 'x/y/z', wildcard: '?/?/?' },
      { url: 'x/y/z', wildcard: 'x/?/?' },
      { url: 'x/y/z', wildcard: '?/?/z' },
      { url: 'x/y/z', wildcard: '?/y/z' },

      { url: 'page', wildcard: 'page/?' },
      { url: 'page', wildcard: 'page/subpage' },
      { url: 'page', wildcard: '?/subpage' },
      { url: 'page/subpage', wildcard: 'page/?' },
      { url: 'page/subpage', wildcard: 'page/?/?' },
      { url: 'page/subpage', wildcard: 'page/subpage/?' },
    ];

    let negTest = [
      { url: 'page', wildcard: 'nonpage' },
      { url: 'page', wildcard: '' },
      { url: 'page/param', wildcard: 'page' },
      { url: 'page/param/param2', wildcard: 'page/?/param' },
    ];

    posTest.forEach((t) => {
      expect(routerHandlerService['isSubscriptionPatternFit'](t.url, t.wildcard)).toBeTrue();
    });

    negTest.forEach((t) => {
      expect(routerHandlerService['isSubscriptionPatternFit'](t.url, t.wildcard)).toBeFalse();
    });

    done();
  });

  it('should work parameter filler', (done) => {
    let test = [
      {
        url: 'page',
        config: {
          wildcard: 'page',
          params: [],
          subscriptions: [],
        },
        shouldBe: {
          url: 'page',
          params: {},
        },
      },
      {
        url: 'page/1234',
        config: {
          wildcard: 'page/?',
          params: [{ param: 'userId', pos: 1 }],
          subscriptions: [],
        },
        shouldBe: {
          url: 'page/1234',
          params: {
            userId: 1234,
          },
        },
      },
      {
        url: 'page/1234/subpage',
        config: {
          wildcard: 'page',
          params: [{ param: 'userId', pos: 1 }],
          subscriptions: [],
        },
        shouldBe: {
          url: 'page/1234/subpage',
          params: {
            userId: 1234,
          },
        },
      },
      {
        url: 'search/cat_videos/cute/2',
        config: {
          wildcard: 'search/?/?/?',
          params: [
            { param: 'query', pos: 1 },
            { param: 'filter', pos: 2 },
            { param: 'page', pos: 3 },
          ],
          subscriptions: [],
        },
        shouldBe: {
          url: 'search/cat_videos/cute/2',
          params: {
            query: 'cat_videos',
            filter: 'cute',
            page: '2',
          },
        },
      },
      {
        url: 'user/1234/profil/email',
        config: {
          wildcard: 'user/?/profil/email',
          params: [{ param: 'userId', pos: 1 }],
          subscriptions: [],
        },
        shouldBe: {
          url: 'user/1234/profil/email',
          params: {
            userId: 1234,
          },
        },
      },
      {
        url: 'page',
        config: {
          wildcard: 'page',
          params: [],
          subscriptions: [],
        },
        shouldBe: {
          url: 'page#subpage',
          params: {},
        },
      },
      {
        url: 'search/cat_videos',
        config: {
          wildcard: 'search/?',
          params: [{ param: 'query', pos: 1 }],
          subscriptions: [],
        },
        shouldBe: {
          url: 'page#subpage',
          params: {
            query: 'cat_videos',
          },
        },
      },
    ];

    test.forEach((t) => {
      let res = routerHandlerService['getParameterUsingRouterPath'](t.url, t.config);

      expect(Object.entries(res).length == Object.entries(t.shouldBe.params).length).toBeTrue();

      for (let key in res) {
        expect(key in t.shouldBe.params && t.shouldBe.params[key] == res[key]).toBeTrue();
      }
      for (let key in t.shouldBe.params) {
        expect(key in res && t.shouldBe.params[key] == res[key]).toBeTrue();
      }
    });

    done();
  });

  it('should work fragment parse', (done) => {
    let cases = [
      {
        url: '',
        fragment: {},
      },
      {
        url: 'f',
        fragment: {
          f: '',
        },
      },
      {
        url: 'f!10',
        fragment: {
          f: 10,
        },
      },
      {
        url: 'f!10;g!20',
        fragment: {
          f: 10,
          g: 20,
        },
      },
    ];

    for (let i = 0; i < cases.length; i++) {
      let res = routerHandlerService.parseFragment(cases[i].url);

      for (let key in res) {
        expect(key in cases[i].fragment && cases[i].fragment[key] == res[key]).toBeTrue();
      }
      for (let key in cases[i].fragment) {
        expect(key in res && cases[i].fragment[key] == res[key]).toBeTrue();
      }
    }
    done();
  });

  it('should generate the correct wildcards', () => {
    let wildcards = [];
    routerHandlerService['config'].forEach((c) => {
      wildcards.push(c.wildcard);
    });
    expect(
      JSON.stringify(wildcards) ==
        JSON.stringify([
          'welcome',
          'test',
          'test2/?',
          'test2/?/?',
          'test2/?/?/test/?',
          'page',
          'page/pagechild1',
          'page/pagechild2',
          'longparent',
          'longparent/firstchild1',
          'longparent/firstchild1/secondchild1',
          'longparent/firstchild1/secondchild2/?/sub',
          'longparent/firstchild2/?',
          '',
        ])
    ).toBeTrue();
  });

  it('should subscribe work', (done) => {
    let handler = () => {};
    routerHandlerService.subscribe('welcome', handler);

    let found = false;

    routerHandlerService['config'].forEach((config) => {
      if (config.wildcard == 'welcome') {
        if (config.subscriptions.indexOf(handler) > -1) {
          found = true;
        }
      }
    });

    expect(found).toBeTrue();
    done();
  });

  it('should unsubscribe work', (done) => {
    let handler = () => {};
    routerHandlerService.subscribe('welcome', handler);
    routerHandlerService.unsubscribe('welcome', handler);

    let found = false;

    routerHandlerService['config'].forEach((config) => {
      if (config.wildcard == 'welcome') {
        if (config.subscriptions.indexOf(handler) > -1) {
          found = true;
        }
      }
    });

    expect(found).toBeFalse();
    done();
  });

  it('should processUrl work simple', (done) => {
    let result = false;

    let handler = (response) => {
      result = true;
    };
    routerHandlerService.subscribe('welcome', handler);

    let res = routerHandlerService['processUrl']('welcome');
    res.routerPath.subscriptions.forEach((s) => {
      s(res.routerResponse);
    });

    expect(result).toBeTrue();
    done();
  });

  it('should processUrl work with params', (done) => {
    let result = '';

    let handler = (response) => {
      result = response.params.param;
    };
    routerHandlerService.subscribe('test2', handler);

    let res = routerHandlerService['processUrl']('test2/42');
    res.routerPath.subscriptions.forEach((s) => {
      s(res.routerResponse);
    });

    expect(result == '42').toBeTrue();
    done();
  });

  it('split should work', (done) => {
    let test = [
      {
        value: 'ASD',
        delimiter: '!',
        result: ['ASD', ''],
      },
      {
        value: 'ASD!BC',
        delimiter: '!',
        result: ['ASD', 'BC'],
      },
      {
        value: 'ASD!BC!FG',
        delimiter: '!',
        result: ['ASD', 'BC!FG'],
      },
      {
        value: 'ASD!!MASD!CASD',
        delimiter: '!',
        result: ['ASD', '!MASD!CASD'],
      },
      {
        value: 'ASD!HEY!!WAKKA',
        delimiter: '!!',
        result: ['ASD!HEY', 'WAKKA'],
      },
      {
        value: 'ASD!!!WAKKAWAKKA!!!!!!WAK',
        delimiter: '!!!',
        result: ['ASD', 'WAKKAWAKKA!!!!!!WAK'],
      },
    ];

    for (let i = 0; i < test.length; i++) {
      let res = routerHandlerService['splitByFirstDelimiter'](test[i].value, test[i].delimiter);
      expect(res.length == test[i].result.length).toBeTrue();

      for (let j = 0; j < res.length; j++) {
        expect(res[j] == test[i].result[j]).toBeTrue();
      }
    }

    done();
  });

  it('should test encode/decode fragment', (done) => {
    let test: { parsed; raw; dontBack? }[] = [
      {
        parsed: { a: '!', b: ';', c: ';!;!;!' },
        raw: 'a!!;b!;;;c!;;!;;!;;!',
      },
      {
        parsed: { a: ';;', b: ';;;', c: ';' },
        raw: 'a!;;;;;b!;;;;;;;c!;;',
      },
      {
        parsed: { '': 'x', a: 'á', b: '$', c: '!' },
        raw: '!x;a!á;b!$;c!!',
      },
      {
        parsed: { '': '', 'ás$:?%+>#&@ó': '~(=/!+ §?_:;>*$<>#&@{}\\|' },
        raw: '!;ás$:?%+>#&@ó!~(=/!+ §?_:;;>*$<>#&@{}\\|',
      },

      // edge case
      {
        parsed: { '': '' },
        raw: '!',
        dontBack: true,
      },
      {
        parsed: { '': '' },
        raw: '!;!',
        dontBack: true,
      },
      {
        parsed: { a: '' },
        raw: 'a!',
        dontBack: true,
      },
      {
        parsed: { '': 'a' },
        raw: '!a',
      },
      {
        parsed: { a: '' },
        raw: 'a',
        dontBack: true,
      },
      {
        parsed: { a: '' },
        raw: 'a;',
        dontBack: true,
      },
      {
        parsed: { a: '' },
        raw: ';a',
        dontBack: true,
      },
      {
        parsed: { a: '', b: '' },
        raw: 'a;b',
        dontBack: true,
      },
      {
        parsed: { a: '', b: 'x' },
        raw: 'a;b!x',
        dontBack: true,
      },
      {
        parsed: { a: '', b: 'x' },
        raw: 'a;b!x',
        dontBack: true,
      },
      {
        parsed: { a: 'x', b: '' },
        raw: 'a!x;b',
        dontBack: true,
      },
    ];

    test.forEach((t) => {
      let parsed = routerHandlerService.parseFragment(t.raw);

      expect(Object.entries(parsed).length == Object.entries(t.parsed).length).toBeTrue();

      for (let key in parsed) {
        expect(key in t.parsed && parsed[key] == t.parsed[key]).toBeTrue();
      }
      for (let key in t.parsed) {
        expect(key in parsed && t.parsed[key] == parsed[key]).toBeTrue();
      }

      let encoded = routerHandlerService.fragmentToRaw(t.parsed);
      if (!t.dontBack) {
        expect(decodeURI(encoded) == t.raw).toBeTrue();
      }
    });

    [{ '!': '' }, { ';': '' }, { 'b!c;a': '' }].forEach((element) => {
      try {
        routerHandlerService.fragmentToRaw(element);
        expect(false).toBeTrue();
      } catch (e) {}
    });

    done();
  });
});
