// ... test imports
import {
  async,
  ComponentFixture,
  inject,
  TestBed, fakeAsync, tick, getTestBed
} from '@angular/core/testing';
import {
  Component,
  CUSTOM_ELEMENTS_SCHEMA,
  DebugElement
} from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { RouterTestingModule } from '@angular/router/testing';
import { By } from '@angular/platform-browser';
import Spy = jasmine.Spy;
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { Store, StoreModule } from "@ngrx/store";

// Load the implementations that should be tested
import { CommonModule } from '@angular/common';

import { Ng2PaginationModule } from 'ng2-pagination';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

import { PaginationComponent } from './pagination.component';
import { PaginationOptions } from '../../core/cache/models/pagination-options.model';
import { MockTranslateLoader } from "../testing/mock-translate-loader";

import { GLOBAL_CONFIG, EnvConfig } from '../../../config';
import { MockStore, MockAction } from "../testing/mock-store";
import { ActivatedRouteStub, RouterStub } from "../testing/router-stubs";

function createTestComponent<T>(html: string, type: {new (...args: any[]): T}): ComponentFixture<T> {
  TestBed.overrideComponent(type, {
    set: { template: html }
  });
  let fixture = TestBed.createComponent(type);

  fixture.detectChanges();
  return fixture as ComponentFixture<T>;
}

function expectPages(fixture: ComponentFixture<any>, pagesDef: string[]): void {
  let de = fixture.debugElement.query(By.css('.pagination'));
  let pages = de.nativeElement.querySelectorAll('li');

  expect(pages.length).toEqual(pagesDef.length);

  for (let i = 0; i < pagesDef.length; i++) {
    let pageDef = pagesDef[i];
    let classIndicator = pageDef.charAt(0);

    if (classIndicator === '+') {
      expect(pages[i].classList.contains("active")).toBeTruthy();
      expect(pages[i].classList.contains("disabled")).toBeFalsy();
      expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1) + ' (current)');
    } else if (classIndicator === '-') {
      expect(pages[i].classList.contains("active")).toBeFalsy();
      expect(pages[i].classList.contains("disabled")).toBeTruthy();
      expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1));
      if (normalizeText(pages[i].textContent) !== '...') {
        expect(pages[i].querySelector('a').getAttribute('tabindex')).toEqual('-1');
      }
    } else {
      expect(pages[i].classList.contains("active")).toBeFalsy();
      expect(pages[i].classList.contains("disabled")).toBeFalsy();
      expect(normalizeText(pages[i].textContent)).toEqual(pageDef);
      if (normalizeText(pages[i].textContent) !== '...') {
        expect(pages[i].querySelector('a').hasAttribute('tabindex')).toBeFalsy();
      }
    }
  }
}

function changePageSize(fixture: ComponentFixture<any>, pageSize: string): void {
  let buttonEl = fixture.nativeElement.querySelector('#paginationControls');
  let activatedRouteStub: ActivatedRouteStub;
  let routerStub: RouterStub;

  buttonEl.click();

  let dropdownMenu = fixture.debugElement.query(By.css('#paginationControlsDropdownMenu'));
  let buttons = dropdownMenu.nativeElement.querySelectorAll('button');

  for (let i = 0; i < buttons.length; i++) {
    if (buttons[i].textContent.trim() == pageSize) {
      buttons[i].click();
      fixture.detectChanges();
      break;
    }
  }
}

function changePage(fixture: ComponentFixture<any>, idx: number): void {
  let de = fixture.debugElement.query(By.css('.pagination'));
  let buttons = de.nativeElement.querySelectorAll('li');

  buttons[idx].querySelector('a').click();
  fixture.detectChanges();
}

function normalizeText(txt: string): string {
  return txt.trim().replace(/\s+/g, ' ');
}

describe('Pagination component', () => {

  let fixture: ComponentFixture<PaginationComponent>;
  let comp: PaginationComponent;
  let testComp: TestComponent;
  let testFixture: ComponentFixture<TestComponent>;
  let de: DebugElement;
  let html;
  let mockStore: any;

  let activatedRouteStub: ActivatedRouteStub;
  let routerStub: RouterStub;

  //Define initial state and test state
  let _initialState = { width: 1600, height: 770, breakPoint: 'xl' };

  // async beforeEach
  beforeEach(async(() => {
    activatedRouteStub = new ActivatedRouteStub();
    routerStub = new RouterStub();
    mockStore = new MockStore(_initialState);

    TestBed.configureTestingModule({
      imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({
        loader: {
          provide: TranslateLoader,
          useClass: MockTranslateLoader
        }
      }), Ng2PaginationModule, NgbModule.forRoot(),
        RouterTestingModule.withRoutes([
          {path: 'home', component: TestComponent}
        ])],
      declarations: [PaginationComponent, TestComponent], // declare the test component
      providers: [
        { provide: ActivatedRoute, useValue: activatedRouteStub },
        { provide: GLOBAL_CONFIG, useValue: EnvConfig },
        { provide: Router, useValue: routerStub },
        { provide: Store, useValue: mockStore },
        PaginationComponent
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    });

  }));

  // synchronous beforeEach
  beforeEach(() => {
    html = `
    <ds-pagination #p="paginationComponent"
                   [paginationOptions]="paginationOptions" 
                   [collectionSize]="collectionSize" 
                   (pageChange)="pageChanged($event)"
                   (pageSizeChange)="pageSizeChanged($event)">
      <ul>
        <li *ngFor="let item of collection | paginate: { itemsPerPage: paginationOptions.pageSize, 
                    currentPage: paginationOptions.currentPage, totalItems: collectionSize }"> {{item}} </li>
      </ul>
    </ds-pagination>`;

    testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
    testComp = testFixture.componentInstance;

  });

  it('should create Pagination Component', inject([PaginationComponent], (app: PaginationComponent) => {
    expect(app).toBeDefined();
  }));

  it('should render', () => {
    expect(testComp.paginationOptions.id).toEqual('test');
    expect(testComp.paginationOptions.currentPage).toEqual(1);
    expect(testComp.paginationOptions.pageSize).toEqual(10);
    expectPages(testFixture, ['-«', '+1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '»']);
  });

  it('should render and respond to page change', () => {
    testComp.collectionSize = 30;

    changePage(testFixture, 3);
    expectPages(testFixture, ['«', '1', '2', '+3', '-»']);

    changePage(testFixture, 0);
    expectPages(testFixture, ['«', '1', '+2', '3', '»']);
  });

  it('should render and respond to collectionSize change', () => {

    testComp.collectionSize = 30;
    testFixture.detectChanges();
    expectPages(testFixture, ['-«', '+1', '2', '3', '»']);

    testComp.collectionSize = 40;
    testFixture.detectChanges();
    expectPages(testFixture, ['-«', '+1', '2', '3', '4', '»']);
  });

  it('should render and respond to pageSize change', () => {

    testComp.collectionSize = 30;
    testFixture.detectChanges();
    expectPages(testFixture, ['-«', '+1', '2', '3', '»']);

    changePageSize(testFixture, '5');
    expectPages(testFixture, ['-«', '+1', '2', '3', '4', '5', '6', '»']);

    changePageSize(testFixture, '10');
    expectPages(testFixture, ['-«', '+1', '2', '3', '»']);

    changePageSize(testFixture, '20');
    expectPages(testFixture, ['-«', '+1', '2', '»']);
  });

  it('should emit pageChange event with correct value', fakeAsync(() => {

    spyOn(testComp, 'pageChanged');

    changePage(testFixture, 3);
    tick();

    expect(testComp.pageChanged).toHaveBeenCalledWith(3);
  }));

  it('should emit pageSizeChange event with correct value', fakeAsync(() => {

    spyOn(testComp, 'pageSizeChanged');

    changePageSize(testFixture, '5');
    tick();

    expect(testComp.pageSizeChanged).toHaveBeenCalledWith(5);
  }));

  it('should set correct route parameters', fakeAsync(() => {
    let paginationComponent: PaginationComponent = testFixture
      .debugElement.query(By.css('ds-pagination')).references['p'];
    routerStub = testFixture.debugElement.injector.get(Router);

    testComp.collectionSize = 60;

    changePage(testFixture, 3);
    tick();
    expect(routerStub.navigate).toHaveBeenCalledWith([{pageId: 'test', page: 3, pageSize: 10}]);
    expect(paginationComponent.currentPage).toEqual(3);

    changePageSize(testFixture, '20');
    tick();
    expect(routerStub.navigate).toHaveBeenCalledWith([{pageId: 'test', page: 3, pageSize: 20}]);
    expect(paginationComponent.pageSize).toEqual(20);
  }));

  it('should get parameters from route', () => {

    activatedRouteStub = testFixture.debugElement.injector.get(ActivatedRoute);
    activatedRouteStub.testParams = {
      pageId: 'test',
      page: 2,
      pageSize: 20
    };

    testFixture.detectChanges();

    expectPages(testFixture, ['«', '1', '+2', '3', '4', '5', '»']);
    expect(testComp.paginationOptions.currentPage).toEqual(2);
    expect(testComp.paginationOptions.pageSize).toEqual(20);

    activatedRouteStub.testParams = {
      pageId: 'test',
      page: 3,
      pageSize: 40
    };

    testFixture.detectChanges();

    expectPages(testFixture, ['«', '1', '2', '+3', '-»']);
    expect(testComp.paginationOptions.currentPage).toEqual(3);
    expect(testComp.paginationOptions.pageSize).toEqual(40);
  });

  it('should respond to windows resize', () => {
    let paginationComponent: PaginationComponent = testFixture
      .debugElement.query(By.css('ds-pagination')).references['p'];
    mockStore = testFixture.debugElement.injector.get(Store);

    mockStore.nextState({ width: 400, height: 770, breakPoint: 'xs' });

    mockStore.select('hostWindow').subscribe((state) => {
      paginationComponent.windowBreakPoint = state;
      testFixture.detectChanges();
      expectPages(testFixture, ['-«', '+1', '2', '3', '4', '5', '-...', '10', '»']);
      de = testFixture.debugElement.query(By.css('ul.pagination'));
      expect(de.nativeElement.classList.contains("pagination-sm")).toBeTruthy();
    });
  });
});

// declare a test component
@Component({selector: 'test-cmp', template: ''})
class TestComponent {

  collection: string[] = [];
  collectionSize: number;
  paginationOptions = new PaginationOptions();

  constructor() {
    this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`);
    this.collectionSize = 100;
    this.paginationOptions.id = 'test';
  }

  pageChanged(page) {
    this.paginationOptions.currentPage = page;
  }

  pageSizeChanged(pageSize) {
    this.paginationOptions.pageSize = pageSize;
  }
}