import {
  ChangeDetectionStrategy,
  DebugElement,
  NO_ERRORS_SCHEMA,
} from '@angular/core';
import {
  ComponentFixture,
  TestBed,
  waitForAsync,
} from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';

import { InputSuggestionsComponent } from './input-suggestions.component';

describe('InputSuggestionsComponent', () => {

  let comp: InputSuggestionsComponent;
  let fixture: ComponentFixture<InputSuggestionsComponent>;
  let de: DebugElement;
  let el: HTMLElement;
  let suggestions;

  beforeEach(waitForAsync(() => {
    suggestions = [{ displayValue: 'suggestion uno', value: 'suggestion uno' }, {
      displayValue: 'suggestion dos',
      value: 'suggestion dos',
    }, { displayValue: 'suggestion tres', value: 'suggestion tres' }];

    TestBed.configureTestingModule({
      imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule, FormsModule, InputSuggestionsComponent],
      providers: [],
      schemas: [NO_ERRORS_SCHEMA],
    }).overrideComponent(InputSuggestionsComponent, {
      set: { changeDetection: ChangeDetectionStrategy.Default },
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(InputSuggestionsComponent);

    comp = fixture.componentInstance; // LoadingComponent test instance
    comp.suggestions = suggestions;
    // query for the message <label> by CSS element selector
    de = fixture.debugElement;
    el = de.nativeElement;
    comp.show.next(true);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(comp).toBeTruthy();
  });

  describe('when the input field is in focus', () => {

    beforeEach(() => {
      const inputElement = de.query(By.css('.suggestion_input'));
      inputElement.nativeElement.focus();
      fixture.detectChanges();

    });

    it('should not have any element in focus', () => {
      const activeElement = el.ownerDocument.activeElement;
      expect(activeElement.nodeName.toLowerCase()).not.toEqual('a');
    });

    describe('when key up is pressed', () => {
      beforeEach(() => {
        spyOn(comp, 'shiftFocusUp');
        const form = de.query(By.css('form'));
        form.triggerEventHandler('keydown.arrowup', {});
        fixture.detectChanges();
      });

      it('should call shiftFocusUp()', () => {
        expect(comp.shiftFocusUp).toHaveBeenCalled();
      });
    });

    describe('when shiftFocusUp() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusUp(new KeyboardEvent('keydown.arrowup'));
        fixture.detectChanges();
      });

      it('should put the focus on the last element ', () => {
        const lastLink = de.query(By.css('.dropdown-list > div:last-child a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(lastLink.nativeElement);
      });
    });

    describe('when key down is pressed', () => {
      beforeEach(() => {
        spyOn(comp, 'shiftFocusDown');
        const form = de.query(By.css('form'));
        form.triggerEventHandler('keydown.arrowdown', {});
        fixture.detectChanges();
      });

      it('should call shiftFocusDown()', () => {
        expect(comp.shiftFocusDown).toHaveBeenCalled();
      });
    });

    describe('when shiftFocusDown() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusDown(new KeyboardEvent('keydown.arrowdown'));
        fixture.detectChanges();
      });

      it('should put the focus on the first element ', () => {
        const firstLink = de.query(By.css('.dropdown-list > div:first-child a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(firstLink.nativeElement);
      });
    });

    describe('when changeFocus() is triggered when selectedIndex is 1', () => {
      beforeEach(() => {
        comp.selectedIndex = 1;
        comp.changeFocus();
        fixture.detectChanges();
      });

      it('should put the focus on the second element', () => {
        const secondLink = de.query(By.css('.dropdown-list > div:nth-child(2) a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(secondLink.nativeElement);
      });
    });
  });

  describe('when the first element is in focus', () => {
    beforeEach(() => {
      const firstLink = de.query(By.css('.dropdown-list > div:first-child a'));
      firstLink.nativeElement.focus();
      comp.selectedIndex = 0;
      fixture.detectChanges();

    });

    describe('when shiftFocusUp() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusUp(new KeyboardEvent('keydown.arrowup'));
        fixture.detectChanges();
      });

      it('should put the focus on the last element ', () => {
        const lastLink = de.query(By.css('.dropdown-list > div:last-child a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(lastLink.nativeElement);
      });
    });

    describe('when shiftFocusDown() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusDown(new KeyboardEvent('keydown.arrowdown'));
        fixture.detectChanges();
      });

      it('should put the focus on the second element ', () => {
        const secondLink = de.query(By.css('.dropdown-list > div:nth-child(2) a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(secondLink.nativeElement);
      });
    });
  });

  describe('when the last element is in focus', () => {
    beforeEach(() => {
      const lastLink = de.query(By.css('.dropdown-list > div:last-child a'));
      lastLink.nativeElement.focus();
      comp.selectedIndex = suggestions.length - 1;
      fixture.detectChanges();

    });

    describe('when shiftFocusUp() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusUp(new KeyboardEvent('keydown.arrowup'));
        fixture.detectChanges();
      });

      it('should put the focus on the second last element ', () => {
        const secondLastLink = de.query(By.css('.dropdown-list > div:nth-last-child(2) a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(secondLastLink.nativeElement);
      });
    });

    describe('when shiftFocusDown() is triggered', () => {
      beforeEach(() => {
        comp.shiftFocusDown(new KeyboardEvent('keydown.arrowdown'));
        fixture.detectChanges();
      });

      it('should put the focus on the first element ', () => {
        const firstLink = de.query(By.css('.dropdown-list > div:first-child a'));
        const activeElement = el.ownerDocument.activeElement;
        expect(activeElement).toEqual(firstLink.nativeElement);
      });
    });

    describe('when any key but is pressed in the form', () => {
      beforeEach(() => {
        spyOn(comp, 'onKeydown');
        const form = de.query(By.css('form'));
        form.triggerEventHandler('keydown', { key: 'Shift' });
        fixture.detectChanges();
      });

      it('should call onKeydown', () => {
        expect(comp.onKeydown).toHaveBeenCalled();
        fixture.detectChanges();
      });
    });
    describe('when onKeydown is triggered with the Enter key', () => {
      beforeEach(() => {
        spyOn(comp.queryInput.nativeElement, 'focus');
        comp.onKeydown(new KeyboardEvent('keydown', { key: 'Enter' }));
        fixture.detectChanges();
      });

      it('should not change the focus', () => {
        expect(comp.queryInput.nativeElement.focus).not.toHaveBeenCalled();
      });

    });

    describe('when onKeydown is triggered with the any other (not-Enter) key', () => {
      beforeEach(() => {
        spyOn(comp.queryInput.nativeElement, 'focus');
        comp.onKeydown(new KeyboardEvent('keydown', { key: 'Shift' }));
        fixture.detectChanges();
      });

      it('should change the focus', () => {
        expect(comp.queryInput.nativeElement.focus).toHaveBeenCalled();
      });

    });
  });

  describe('when the suggestions list is not empty and show is true', () => {
    beforeEach(() => {
      comp.show.next(true);
      fixture.detectChanges();
    });
    it('should contain an .autocomplete list with a \'show\' class', () => {
      const autocomplete = de.query(By.css('div.autocomplete'));
      expect(autocomplete.nativeElement.classList).toContain('show');
    });
  });

  describe('when the suggestions list is not empty and show is false', () => {
    beforeEach(() => {
      comp.show.next(false);
      fixture.detectChanges();
    });
    it('should contain an .autocomplete list without a \'show\' class', () => {
      const autocomplete = de.query(By.css('div.autocomplete'));
      expect(autocomplete.nativeElement.classList).not.toContain('show');
    });
  });

  describe('when the suggestions list is empty and show is false', () => {
    beforeEach(() => {
      comp.suggestions = [];
      comp.show.next(false);
      fixture.detectChanges();
    });
    it('should contain an .autocomplete list without a \'show\' class', () => {
      const autocomplete = de.query(By.css('div.autocomplete'));
      expect(autocomplete.nativeElement.classList).not.toContain('show');
    });
  });
  describe('when the suggestions list is empty and show is true', () => {
    beforeEach(() => {
      comp.suggestions = [];
      comp.show.next(true);
      fixture.detectChanges();
    });
    it('should contain an .autocomplete list without a \'show\' class', () => {
      const autocomplete = de.query(By.css('div.autocomplete'));
      expect(autocomplete.nativeElement.classList).not.toContain('show');
    });
  });

  describe('when the variable \'show\' is set to true and close() is called', () => {
    beforeEach(() => {
      comp.show.next(true);
      comp.close();
      fixture.detectChanges();
    });
    it('should set \'show\' to false', () => {
      expect(comp.show.getValue()).toBeFalsy();
    });
  });

  describe('when an element is clicked', () => {
    const clickedIndex = 0;
    beforeEach(() => {
      spyOn(comp, 'onClickSuggestion');
      const clickedLink = de.query(By.css('.dropdown-list > div:nth-child(' + (clickedIndex + 1) + ') a'));
      clickedLink.triggerEventHandler('click', {});
      fixture.detectChanges();
    });
    it('should call onClickSuggestion() with the suggestion as a parameter', () => {
      expect(comp.onClickSuggestion).toHaveBeenCalledWith(suggestions[clickedIndex].value);
    });
  });
});