import { DOCUMENT } from '@angular/common';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Component, DebugElement, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Router, RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ACLService } from '@ohayo/acl';
import { OhayoThemeModule, MenuIcon, MenuService, SettingsService, WINDOW } from '@ohayo/theme';
import { deepCopy } from '@ohayo/util';
import { SidebarNavComponent } from './sidebar-nav.component';
import { SidebarNavModule } from './sidebar-nav.module';
import { Nav } from './sidebar-nav.types';

const floatingShowCls = '.sidebar-nav__floating-show';
const MOCKMENUS = [
  {
    text: '主导航',
    group: true,
    children: [
      {
        text: '仪表盘',
        children: [
          { text: 'v1', link: '/v1' },
          { text: 'v2', link: '#/v2', i18n: 'v2-i18n' },
          { text: 'v3' },
          {
            text: 'externalLink-blank',
            externalLink: '//ohayojp.com/blank',
            target: '_blank',
          },
          {
            text: 'externalLink-top',
            externalLink: '//ohayojp.com/top',
            target: '_top',
          },
        ],
      },
    ],
  },
] as Nav[];

const MOCKOPENSTRICTLY = [
  {
    text: '',
    group: true,
    children: [
      {
        text: '',
        link: '/v1',
        open: true,
        children: [{ text: '' }],
      },
      {
        text: '',
        link: '/v1',
        open: true,
        children: [{ text: '' }],
      },
    ],
  },
] as Nav[];

class MockACLService {
  can(val: string): boolean {
    return val === 'admin';
  }
}

class MockWindow {
  location = new MockLocation();
  open(): void { }
}
class MockLocation {
  private url: string;
  get href(): string {
    return this.url;
  }
  set href(url: string) {
    this.url = url;
  }
}

describe('components: sidebar-nav', () => {
  let fixture: ComponentFixture<TestComponent>;
  let dl: DebugElement;
  let context: TestComponent;
  let router: Router;
  let setSrv: SettingsService;
  let menuSrv: MenuService;
  let page: PageObject;
  let doc: Document;

  function createModule(): void {
    TestBed.configureTestingModule({
      imports: [RouterModule.forRoot([]), OhayoThemeModule, HttpClientTestingModule, SidebarNavModule],
      declarations: [TestComponent],
      providers: [
        { provide: ACLService, useClass: MockACLService },
        { provide: WINDOW, useFactory: () => new MockWindow() },
      ],
    });
  }

  function createComp(needMockNavigateByUrl: boolean = true, callback?: () => void): void {
    fixture = TestBed.createComponent(TestComponent);
    dl = fixture.debugElement;
    context = fixture.componentInstance;
    fixture.detectChanges();
    router = TestBed.inject<Router>(Router);
    setSrv = TestBed.inject<SettingsService>(SettingsService);
    menuSrv = TestBed.inject<MenuService>(MenuService);
    doc = TestBed.inject(DOCUMENT);
    menuSrv.add(deepCopy(MOCKMENUS));
    page = new PageObject();
    if (needMockNavigateByUrl) spyOn(router, 'navigateByUrl');
    if (callback) callback();
  }

  afterEach(() => context.comp.ngOnDestroy());

  describe('', () => {
    beforeEach(() => createModule());

    describe('[default]', () => {
      it('should be navigate url', () => {
        createComp();
        spyOn(context, 'select');
        const data = deepCopy(MOCKMENUS);
        menuSrv.add(data);
        expect(context.select).not.toHaveBeenCalled();
        expect(router.navigateByUrl).not.toHaveBeenCalled();
        const itemEl = page.getEl<HTMLElement>('.sidebar-nav__depth1 a');
        itemEl!.click();
        fixture.detectChanges();
        expect(context.select).toHaveBeenCalled();
        expect(router.navigateByUrl).toHaveBeenCalled();
      });

      describe('should be navigate external link', () => {
        it('with target is _blank', () => {
          createComp();
          const win = TestBed.inject(WINDOW) as MockWindow;
          spyOn(win, 'open');
          const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="6"]');
          itemEl!.click();
          expect(win.open).toHaveBeenCalled();
        });
        it('with target is _top', () => {
          createComp();
          const win = TestBed.inject(WINDOW) as MockWindow;
          const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="7"]');
          itemEl!.click();
          expect(win.location.href).toBe(`//ohayojp.com/top`);
        });
      });

      it('should be hide group name', () => {
        createComp();
        page.checkCount('.sidebar-nav__group-title');
        const data = deepCopy(MOCKMENUS) as Nav[];
        data[0].group = false;
        menuSrv.add(data);
        fixture.detectChanges();
        page.checkCount('.sidebar-nav__group-title', 0);
      });

      it('should be toggle open', () => {
        createComp();
        const data = deepCopy(MOCKMENUS);
        menuSrv.add(data);
        expect(data[0].children[0]._open).toBeUndefined();
        const subTitleEl = page.getEl<HTMLElement>('.sidebar-nav__item-link');
        subTitleEl!.click();
        fixture.detectChanges();
        expect(data[0].children[0]._open).toBe(true);
      });

      it('should be reset menu when service is changed', () => {
        createComp();
        page.checkText('.sidebar-nav__group-title', MOCKMENUS[0].text);
        const newMenu = deepCopy(MOCKMENUS);
        newMenu[0].text = 'new主导航';
        menuSrv.add(newMenu);
        fixture.detectChanges();
        page.checkText('.sidebar-nav__group-title', newMenu[0].text);
      });

      it('should be block click menu when is disabled', () => {
        createComp();
        spyOn(context, 'select');
        const newMenus = [
          {
            text: '',
            children: [{ text: 'new menu', disabled: true }],
          },
        ];
        menuSrv.add(newMenus);
        expect(context.select).not.toHaveBeenCalled();
        const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item-disabled');
        itemEl!.click();
        fixture.detectChanges();
        expect(context.select).toHaveBeenCalled();
      });

      it('should be support html in text or i18n', () => {
        createComp();
        menuSrv.add([{ text: 'text <strong>1</strong>' }]);
        page.checkText('.sidebar-nav__item', `text 1`);
        menuSrv.add([{ i18n: 'i18n <strong>1</strong>' }]);
        page.checkText('.sidebar-nav__item', `i18n 1`);
      });
    });

    describe('#icon', () => {
      function updateIcon(icon: string | MenuIcon): void {
        createComp();

        menuSrv.add([
          {
            text: '',
            group: true,
            children: [
              {
                text: '',
                icon,
              },
            ],
          },
        ] as Nav[]);

        fixture.detectChanges();
      }
      describe('with icon', () => {
        it('when is string and includes [anticon-]', () => {
          updateIcon('anticon-edit');
          const el = page.getEl('.sidebar-nav__item-icon') as HTMLElement;
          expect(el.classList).toContain('anticon-edit');
        });
        it('when is string and http prefix', () => {
          updateIcon('http://ohayojp/1.jpg');
          page.checkCount('.sidebar-nav__item-img', 1);
        });
        it('when is class string', () => {
          updateIcon('demo-class');
          page.checkCount('.demo-class', 1);
        });
      });
      it('with className', () => {
        updateIcon({ type: 'class', value: 'demo-class' });
        page.checkCount('.demo-class', 1);
      });
      it('with img', () => {
        updateIcon({ type: 'img', value: '1.jpg' });
        page.checkCount('.sidebar-nav__item-img', 1);
      });
    });

    describe('[collapsed]', () => {
      describe('#default', () => {
        beforeEach(() => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
        });
        it(`should be won't show sub-menu when not collapse`, () => {
          setSrv.layout.collapsed = false;
          fixture.detectChanges();
          page.showSubMenu(false);
        });
        it('should be show sub-menu', () => {
          page.showSubMenu();
        });
        it('should be displayed full submenu', () => {
          const clientHeight = spyOnProperty(doc.documentElement, 'clientHeight').and.returnValue(0);
          spyOnProperty(doc.querySelector('body')!, 'clientHeight').and.returnValue(0);
          expect(clientHeight).not.toHaveBeenCalled();
          page.showSubMenu();
          expect(clientHeight).toHaveBeenCalled();
        });
        it('should be working when include badge', () => {
          const mockMenu = deepCopy(MOCKMENUS) as Nav[];
          mockMenu[0].children![0].badge = 1;
          menuSrv.add(mockMenu);
          fixture.detectChanges();
          expect(page.getEl('.badge') != null).toBe(true);
          page.showSubMenu();
          expect(page.getEl('.sidebar-nav__floating-container .sidebar-nav__item', true) != null).toBe(true);
        });
        it('should be ingore children title trigger event', () => {
          spyOn(context, 'select');
          expect(context.select).not.toHaveBeenCalled();
          const mockMenu = deepCopy(MOCKMENUS) as Nav[];
          mockMenu[0].children![0].children = [{ text: 'a', children: [{ text: 'b' }] }];
          menuSrv.add(mockMenu);
          fixture.detectChanges();
          page.showSubMenu();
          const containerEl = page.getEl<HTMLElement>(floatingShowCls, true)!;
          (containerEl.querySelector('.sidebar-nav__item-link') as HTMLElement).click();
          expect(context.select).not.toHaveBeenCalled();
        });
      });
      describe('should be hide sub-menu in floating container', () => {
        it('muse be hide via click menu link', () => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
          page.showSubMenu();
          page.hideSubMenu();
          expect(router.navigateByUrl).toHaveBeenCalled();
        });
        it('muse be hide via mouse leave area', () => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
          page.showSubMenu();
          page.getEl<HTMLElement>(floatingShowCls, true)!.dispatchEvent(new Event('mouseleave'));
          fixture.detectChanges();
          expect(page.getEl<HTMLElement>(floatingShowCls, true)).toBeNull();
        });
        it('muse be not hide via click except menu link area', () => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
          page.showSubMenu();
          const containerEl = page.getEl<HTMLElement>(floatingShowCls, true);
          containerEl!.querySelectorAll('li')[1].click();
          fixture.detectChanges();
          expect(router.navigateByUrl).not.toHaveBeenCalled();
        });
        it('muse be hide via click span of menu item', () => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
          page.showSubMenu();
          const containerEl = page.getEl<HTMLElement>(floatingShowCls, true);
          containerEl!.querySelectorAll('span')[1].click();
          fixture.detectChanges();
          expect(router.navigateByUrl).toHaveBeenCalled();
        });
        it('muse be hide via document click', () => {
          createComp();
          setSrv.layout.collapsed = true;
          fixture.detectChanges();
          page.showSubMenu();
          document.dispatchEvent(new MouseEvent('click'));
          fixture.detectChanges();
          expect(router.navigateByUrl).not.toHaveBeenCalled();
        });
      });
      it('#52', () => {
        createComp();
        setSrv.layout.collapsed = true;
        fixture.detectChanges();
        page.showSubMenu();
        // tslint:disable-next-line: no-string-literal
        spyOn(context.comp['floatingEl'], 'remove');
        page.hideSubMenu();
        expect(page.getEl<HTMLElement>(floatingShowCls, true)).toBeNull();
      });
    });

    describe('#disabledAcl', () => {
      const newMenus = [
        {
          text: '',
          children: [
            { text: 'new menu', acl: 'admin' },
            { text: 'new menu', acl: 'user' },
          ],
        },
      ];
      beforeEach(() => createComp());
      it('should be disabled item when with true', () => {
        context.disabledAcl = true;
        fixture.detectChanges();
        menuSrv.add(newMenus);
        const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="3"]');
        expect(itemEl!.classList).toContain('sidebar-nav__item-disabled');
      });
      it('should be hidden item when with false', () => {
        context.disabledAcl = false;
        fixture.detectChanges();
        menuSrv.add(newMenus);
        const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="3"]');
        expect(itemEl == null).toBe(true);
      });
    });

    describe('#openStrictly', () => {
      beforeEach(() => {
        createComp();
        context.openStrictly = true;
        fixture.detectChanges();
        menuSrv.add(deepCopy(MOCKOPENSTRICTLY));
        fixture.detectChanges();
      });
      it('should working', () => {
        page.checkCount('.sidebar-nav__open', 2);
      });
      it(`should be won't close other item`, () => {
        const list = dl.queryAll(By.css('.sidebar-nav__item-link'));
        expect(list.length).toBe(4);
        (list[0].nativeElement as HTMLElement).click();
        fixture.detectChanges();
        page.checkCount('.sidebar-nav__open', 1);
        (list[2].nativeElement as HTMLElement).click();
        fixture.detectChanges();
        page.checkCount('.sidebar-nav__open', 0);
      });
    });
  });

  describe('[underPad]', () => {
    beforeEach(createModule);

    it('should be auto collapsed when less than pad', fakeAsync(() => {
      // create test component
      TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`);
      const defaultCollapsed = false;
      createComp(false, () => {
        spyOnProperty(window, 'innerWidth').and.returnValue(767);
        setSrv.layout.collapsed = defaultCollapsed;
        fixture.detectChanges();
      });
      router.navigateByUrl('/');
      fixture.detectChanges();
      tick(20);
      expect(setSrv.layout.collapsed).toBe(!defaultCollapsed);
    }));
    it(`should be won't collapsed when more than pad`, fakeAsync(() => {
      // create test component
      TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`);
      const defaultCollapsed = false;
      createComp(false, () => {
        spyOnProperty(window, 'innerWidth').and.returnValue(769);
        setSrv.layout.collapsed = defaultCollapsed;
        fixture.detectChanges();
      });
      router.navigateByUrl('/');
      fixture.detectChanges();
      tick(1000);
      expect(setSrv.layout.collapsed).toBe(defaultCollapsed);
    }));
    it('should be auto expaned when less than pad trigger click', fakeAsync(() => {
      // create test component
      TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`);
      createComp();
      setSrv.layout.collapsed = true;
      fixture.detectChanges();
      spyOnProperty(window, 'innerWidth').and.returnValue(767);
      expect(setSrv.layout.collapsed).toBe(true);
      page.getEl<HTMLElement>('.sidebar-nav')!.click();
      fixture.detectChanges();
      tick(20);
      expect(setSrv.layout.collapsed).toBe(false);
    }));
  });

  describe('should be recursive path', () => {
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [
          RouterModule.forRoot([]),
          OhayoThemeModule,
          SidebarNavModule,
          RouterTestingModule.withRoutes([
            { path: 'user', component: TestRouteComponent },
            { path: 'user2', component: TestRouteComponent },
            { path: 'user/type', component: TestRouteComponent },
          ]),
        ],
        declarations: [TestComponent, TestRouteComponent],
      });
    });
    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(TestComponent);
      dl = fixture.debugElement;
      context = fixture.componentInstance;
      menuSrv = TestBed.inject<MenuService>(MenuService);
      fixture.detectChanges();
      createComp(false);
      menuSrv.add([
        {
          text: '主导航',
          group: true,
          children: [
            { text: 'user1', link: '/user' },
            { text: 'user2', link: '/user' },
          ],
        },
      ]);
    }));
    it('with true', fakeAsync(() => {
      context.recursivePath = true;
      fixture.detectChanges();
      router.navigateByUrl('/user2');
      tick();
      fixture.detectChanges();
      page.checkCount('.sidebar-nav__selected', 0);
      router.navigateByUrl('/user/type');
      tick();
      fixture.detectChanges();
      page.checkCount('.sidebar-nav__selected', 1);
    }));
    it('with false', fakeAsync(() => {
      context.recursivePath = false;
      fixture.detectChanges();
      router.navigateByUrl('/user2');
      tick();
      fixture.detectChanges();
      page.checkCount('.sidebar-nav__selected', 0);
      router.navigateByUrl('/user/type');
      tick();
      fixture.detectChanges();
      page.checkCount('.sidebar-nav__selected', 0);
    }));
    it('should be ingore _open when enabled openStrictly', fakeAsync(() => {
      context.openStrictly = true;
      fixture.detectChanges();
      menuSrv.add(deepCopy(MOCKOPENSTRICTLY));
      page.checkCount('.sidebar-nav__open', 2);
      router.navigateByUrl('/user2');
      fixture.detectChanges();
      page.checkCount('.sidebar-nav__open', 2);
    }));
  });

  class PageObject {
    getEl<T>(cls: string, body: boolean = false): T | null {
      const el = body ? document.querySelector(cls) : dl.query(By.css(cls)) ? dl.query(By.css(cls)).nativeElement : null;
      return el ? (el as T) : null;
    }
    checkText(cls: string, value: any): void {
      const el = this.getEl<HTMLElement>(cls);
      expect(el ? el.innerText.trim() : '').toBe(value);
    }
    checkCount(cls: string, count: number = 1): this {
      expect(dl.queryAll(By.css(cls)).length).toBe(count);
      return this;
    }
    /** 期望显示子菜单，默认：`true` */
    showSubMenu(resultExpectShow: boolean = true): void {
      let conEl = this.getEl<HTMLElement>(floatingShowCls, true);
      expect(conEl).toBeNull();
      const subTitleEl = this.getEl<HTMLElement>('.sidebar-nav__item-link');
      subTitleEl!.dispatchEvent(new Event('mouseenter'));
      fixture.detectChanges();
      conEl = this.getEl<HTMLElement>(floatingShowCls, true);
      if (resultExpectShow) {
        expect(conEl).not.toBeNull();
      } else {
        expect(conEl).toBeNull();
      }
    }
    /** 期望隐藏子菜单，默认：`true` */
    hideSubMenu(resultExpectHide: boolean = true): void {
      const containerEl = this.getEl<HTMLElement>(floatingShowCls, true);
      expect(containerEl).not.toBeNull();
      containerEl!.querySelector(resultExpectHide ? 'a' : 'li')!.click();
      fixture.detectChanges();
      const conEl = this.getEl<HTMLElement>(floatingShowCls, true);
      if (resultExpectHide) expect(conEl).toBeNull();
      else expect(conEl).not.toBeNull();
    }
  }
});

@Component({
  template: `
    <sidebar-nav
      #comp
      [disabledAcl]="disabledAcl"
      [autoCloseUnderPad]="autoCloseUnderPad"
      [recursivePath]="recursivePath"
      [openStrictly]="openStrictly"
      (select)="select()"
    ></sidebar-nav>
  `,
})
class TestComponent {
  @ViewChild('comp', { static: true })
  comp: SidebarNavComponent;
  disabledAcl = false;
  autoCloseUnderPad = false;
  recursivePath = false;
  openStrictly = false;
  select(): void { }
}

@Component({ template: `` })
class TestRouteComponent { }
