/*
 * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
import { InteractivityChecker } from '@angular/cdk/a11y';
import { ComponentRef, ElementRef, Renderer2, ViewContainerRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { TooltipComponent } from './tooltip.component';
import { TooltipDirective } from './tooltip.directive';

class MockElementRef extends ElementRef {
  nativeElement = {
    contains: jest.fn(),
    appendChild: jest.fn(),
  };
}

describe('TooltipDirective', () => {
  let directive: TooltipDirective;
  const mockComponentRef: ComponentRef<TooltipComponent> = {
    setInput: jest.fn(),
    destroy: jest.fn(),
    onDestroy: jest.fn(),
    componentType: TooltipComponent,
    changeDetectorRef: null,
    location: null,
    hostView: null,
    injector: null,
    instance: { id: '', left: 0, top: 0, text: '', show: false },
  };

  beforeEach((): void => {
    TestBed.configureTestingModule({
      providers: [ViewContainerRef, { provide: ElementRef, useClass: MockElementRef }, Renderer2, InteractivityChecker],
    });
    TestBed.runInInjectionContext(() => {
      directive = new TooltipDirective();
    });
  });

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

  describe('ngAfterViewInit', () => {
    it('should create tooltip', () => {
      directive.createTooltip = jest.fn();

      directive.ngAfterViewInit();

      expect(directive.createTooltip).toHaveBeenCalled();
    });
  });

  describe('ngOnDestroy', () => {
    it('should destroy tooltip', () => {
      directive.destroy = jest.fn();

      directive.ngOnDestroy();

      expect(directive.destroy).toHaveBeenCalled();
    });
  });

  describe('createTooltip', () => {
    beforeEach(() => {
      directive.viewContainerRef.createComponent = jest.fn().mockReturnValue({ location: { nativeElement: {} } });
      directive.setAriaLabeledBy = jest.fn();
      directive.getFocusableElement = jest.fn().mockReturnValue({ appendChild: jest.fn() });
      directive.interactivityChecker.isFocusable = jest.fn();
    });

    it('should create tooltip component', () => {
      directive.createTooltip();

      expect(directive.viewContainerRef.createComponent).toHaveBeenCalled();
    });

    it('should check if parent element focusable', () => {
      directive.createTooltip();

      expect(directive.interactivityChecker.isFocusable).toHaveBeenCalled();
    });

    it('should get focusable element if parent not focusable', () => {
      directive.createTooltip();

      expect(directive.getFocusableElement).toHaveBeenCalled();
    });

    it('should insert tooltip component to focusable', () => {
      directive.createTooltip();

      expect(directive.focusableElement.appendChild).toHaveBeenCalled();
    });

    it('should set aria-labeledby attribute to parent', () => {
      directive.createTooltip();

      expect(directive.setAriaLabeledBy).toHaveBeenCalled();
    });
  });

  describe('showTooltip', () => {
    beforeEach(() => {
      directive.setTooltipProperties = jest.fn();
      directive.elementRef.nativeElement.contains = jest.fn().mockReturnValue(true);
    });

    it('should check if element focused', () => {
      directive.showTooltip();

      expect(directive.elementRef.nativeElement.contains).toHaveBeenCalled();
    });

    it('should set tooltip properties', () => {
      directive.showTooltip();

      expect(directive.setTooltipProperties).toHaveBeenCalledWith(true);
    });
  });

  describe('hideTooltip', () => {
    it('should hide tooltip', () => {
      directive.hide = jest.fn();

      directive.hideTooltip();

      expect(directive.hide).toHaveBeenCalled();
    });
  });

  describe('onKeydown', () => {
    it('should hide tooltip if escape key pressed', () => {
      directive.hide = jest.fn();
      const escapeEvent: KeyboardEvent = { ...new KeyboardEvent('esc'), key: 'Escape' };

      directive.onKeydown(escapeEvent);

      expect(directive.hide).toHaveBeenCalled();
    });
  });

  describe('setTooltipProperties', () => {
    beforeEach(() => {
      directive.componentRef = mockComponentRef;
      directive.elementRef.nativeElement.getBoundingClientRect = jest
        .fn()
        .mockReturnValue({ left: 0, right: 1000, bottom: 1000 });
    });

    it('should get bounding client rect', () => {
      directive.setTooltipProperties();

      expect(directive.elementRef.nativeElement.getBoundingClientRect).toHaveBeenCalled();
    });

    it('should set tooltip instance properties', () => {
      directive.tooltip = 'I am tooltip';
      directive.tooltipId = 'tooltip-1';

      directive.setTooltipProperties();

      expect(directive.componentRef.instance).toStrictEqual({
        id: 'tooltip-1',
        left: 500,
        text: 'I am tooltip',
        top: 1000,
        show: true,
      });
    });

    it('should add margin if parent element focused', () => {
      directive.setTooltipProperties(true);

      expect(directive.componentRef.instance.top).toBe(1004);
    });
  });

  describe('setAriaLabeledBy', () => {
    beforeEach(() => {
      directive.renderer.setAttribute = jest.fn();
    });

    it('should set aria-labeledby attribute', () => {
      directive.setAriaLabeledBy();

      expect(directive.renderer.setAttribute).toHaveBeenCalled();
    });
  });

  describe('removeAriaLabeledBy', () => {
    beforeEach(() => {
      directive.renderer.removeAttribute = jest.fn();
    });

    it('should remove aria-labeledby attribute', () => {
      directive.removeAriaLabeledBy();

      expect(directive.renderer.removeAttribute).toHaveBeenCalled();
    });
  });

  describe('getFocusableElement', () => {
    it('should return null', () => {
      const simpleElement = document.createElement('a');

      const result: HTMLElement = directive.getFocusableElement(simpleElement);

      expect(result).toBeNull();
    });

    it('should return focusable child element', () => {
      const nestedElement = document.createElement('div');
      const buttonElement = document.createElement('button');
      nestedElement.appendChild(buttonElement);

      const result: HTMLElement = directive.getFocusableElement(nestedElement);

      expect(result).toBe(buttonElement);
    });
  });

  describe('hide', () => {
    beforeEach(() => {
      directive.componentRef = Object.assign(mockComponentRef, { instance: { ...mockComponentRef.instance, show: true } });
    });

    it('should hide component', () => {
      directive.hide();

      expect(directive.componentRef.instance.show).toBeFalsy;
    });
  });

  describe('destroy', () => {
    beforeEach(() => {
      directive.componentRef = mockComponentRef;
      directive.removeAriaLabeledBy = jest.fn();
    });

    it('should set component ref to null', () => {
      directive.destroy();

      expect(directive.componentRef).toBeNull();
    });

    it('should remove aria-labeledby attribute', () => {
      directive.destroy();

      expect(directive.removeAriaLabeledBy).toHaveBeenCalled();
    });

    it('should set focusable element to null', () => {
      directive.destroy();

      expect(directive.focusableElement).toBeNull();
    });
  });
});