import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { Component, DebugElement } from '@angular/core';
import { BixiTableModule, EColType, ICols } from '@bixi/core/table';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { NzTableModule } from 'ng-zorro-antd/table';
import { BixiTableColCheckboxComponent } from '@bixi/core/table/cols/col-checkbox.component';
import { By } from '@angular/platform-browser';
import { NzCheckboxComponent } from 'ng-zorro-antd';

describe('col checkbox', () => {
  let fixture: ComponentFixture<TestComponent>;
  let context: TestComponent;
  let debugEle: DebugElement;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [BixiTableModule, BrowserAnimationsModule, NzToolTipModule, NoopAnimationsModule, NzTableModule],
      declarations: [TestComponent]
    });

    fixture = TestBed.createComponent(TestComponent);
    context = fixture.componentInstance;
    debugEle = fixture.debugElement;
  });

  // @ts-ignore
  const tickChanges = () => {
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
  };

  // @ts-ignore
  const flushChanges = () => {
    fixture.detectChanges();
    flush();
    fixture.detectChanges();
  };

  it('#selectedKeys should init col checked status', () => {
    // given
    context.rows = rows;
    context.selectedKeys = ['1'];

    // when
    fixture.detectChanges();
    const checkBoxWrapper = debugEle.query(By.directive(BixiTableColCheckboxComponent)).query(By.css('.ant-checkbox')).nativeElement as HTMLSpanElement;

    // then
    expect(checkBoxWrapper.classList.toString()).toContain('ant-checkbox-checked');
  });

  it('#selectedKeys should keep correct value when rows changed', fakeAsync(() => {
    // given
    const rows1 = rows;
    const rows2 = (new Array(10)).fill('').map((v: string, i: number) => ({
      id: `${ i + 11 }`,
      name: `name_${ i }${ v }`
    }));
    let checkBoxList: DebugElement[] = [];
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id'
      }
    ];
    context.rows = rows1;
    context.selectedKeys = [];

    // when
    fixture.detectChanges();
    checkBoxList = debugEle.queryAll(By.directive(NzCheckboxComponent));
    checkBoxList[1].nativeElement.click();
    flushChanges();

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(1);
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith([ '1' ]);

    // when
    context.rows = rows2;
    fixture.detectChanges();
    checkBoxList = debugEle.queryAll(By.directive(NzCheckboxComponent));
    checkBoxList[1].nativeElement.click();
    flushChanges();

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(2);
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith([ '1', '11' ]);

    // when
    context.rows = rows1;
    fixture.detectChanges();

    // then
    checkBoxList = debugEle.queryAll(By.directive(NzCheckboxComponent));
    expect(checkBoxList[1].query(By.css('.ant-checkbox-checked'))).toBeTruthy();

    // when
    context.rows = rows2;
    fixture.detectChanges();

    // then
    checkBoxList = debugEle.queryAll(By.directive(NzCheckboxComponent));
    expect(checkBoxList[1].query(By.css('.ant-checkbox-checked'))).toBeTruthy();
  }));

  it('#selectedKeysChange should work when row checked', fakeAsync(() => {
    // given
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id'
      }
    ];
    context.selectedKeys = [];

    // when
    fixture.detectChanges();
    const checkBox = debugEle.queryAll(By.directive(NzCheckboxComponent))[1].nativeElement as HTMLSpanElement;

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(0);

    // when
    checkBox.click();
    flushChanges();

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(1);
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1']);
  }));

  it('#selectable should work', () => {
    // given
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id',
        selectable: val => val !== '2'
      }
    ];

    // when
    fixture.detectChanges();
    const checkbox = debugEle.queryAll(By.directive(BixiTableColCheckboxComponent))[1].query(By.css('.ant-checkbox-disabled'));

    // then
    expect(checkbox).toBeTruthy();
  });

  it('should rows all checked when header checkbox checked', fakeAsync(() => {
    // given
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id'
      }
    ];
    context.selectedKeys = [];

    // when
    fixture.detectChanges();
    const checkbox = debugEle.query(By.directive(NzCheckboxComponent)).nativeElement as HTMLSpanElement;
    checkbox.click();
    flushChanges();

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(1);
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']);
  }));

  it('should unselectable rows unchecked when header checkbox checked', fakeAsync(() => {
    // given
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id',
        selectable: val => val !== '2'
      }
    ];
    context.selectedKeys = [];

    // when
    fixture.detectChanges();
    const checkbox = debugEle.query(By.directive(NzCheckboxComponent)).nativeElement as HTMLSpanElement;
    checkbox.click();
    flushChanges();

    // then
    expect(context.onSelectedKeysChange).toHaveBeenCalledTimes(1);
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '3', '4', '5', '6', '7', '8', '9', '10']);
  }));

  it('should header checkbox render correct checked status', fakeAsync(() => {
    // given
    context.cols = [
      {
        type: EColType.checkbox,
        key: 'id'
      }
    ];
    context.selectedKeys = [];
    fixture.detectChanges();

    const headerCheckbox = debugEle.query(By.directive(NzCheckboxComponent));
    const checkBoxList = debugEle.queryAll(By.directive(NzCheckboxComponent));
    expect(headerCheckbox.query(By.css('.ant-checkbox-checked'))).not.toBeTruthy();

    checkBoxList[1].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[2].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[3].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[4].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[5].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[6].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[7].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6', '7']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[8].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6', '7', '8']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[9].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6', '7', '8', '9']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).toBeTruthy();

    checkBoxList[10].nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']);
    expect(headerCheckbox.query(By.css('.ant-checkbox-indeterminate'))).not.toBeTruthy();
    expect(headerCheckbox.query(By.css('.ant-checkbox-checked'))).toBeTruthy();

    headerCheckbox.nativeElement.click();
    flushChanges();
    expect(context.onSelectedKeysChange).toHaveBeenCalledWith([]);
  }));
});

const rows = ((new Array(10)).fill('') as any)
  .map((v: string, i: number) => {
    return {
      id: `${i + 1}`,
      name: `name_${i}${v}`
    };
  });

@Component({
  template: `
    <bixi-table
      [(selectedKeys)]="selectedKeys"
      [rows]="rows"
      [cols]="cols"
      (selectedKeysChange)="onSelectedKeysChange($event)"
    ></bixi-table>`
})
class TestComponent {
  selectedKeys: string[] = [];
  rows = rows;
  cols: ICols = [{ type: EColType.checkbox, key: 'id' }];
  onSelectedKeysChange = jasmine.createSpy('selectedKeysChange callback');
}
