import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NumberInputComponent, NumberInputValidation } from './number-input.component';

describe('NumberInputComponent', () => {
  let component: NumberInputComponent;
  let fixture: ComponentFixture<NumberInputComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [NumberInputComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(NumberInputComponent);
    component = fixture.componentInstance;
  });

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

  describe('message input', () => {
    it('should display the message text', () => {
      // Arrange
      const testMessage = 'Enter a number';
      fixture.componentRef.setInput('message', testMessage);

      // Act
      fixture.detectChanges();

      // Assert
      const messageElement: HTMLElement = fixture.nativeElement.querySelector('[data-what="message"]');
      expect(messageElement.textContent?.trim()).toBe(testMessage);
    });
  });

  describe('inputLabel input', () => {
    it('should set placeholder when inputLabel is provided', () => {
      // Arrange
      const testLabel = 'Amount';
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputLabel', testLabel);

      // Act
      fixture.detectChanges();

      // Assert
      const inputElement: HTMLInputElement = fixture.nativeElement.querySelector('[data-what="input"]');
      expect(inputElement.placeholder).toBe(testLabel);
    });

    it('should set empty placeholder when inputLabel is undefined', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputLabel', undefined);

      // Act
      fixture.detectChanges();

      // Assert
      const inputElement: HTMLInputElement = fixture.nativeElement.querySelector('[data-what="input"]');
      expect(inputElement.placeholder).toBe('');
    });
  });

  describe('inputValue model', () => {
    it('should set control value when inputValue changes', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const testValue = 42;

      // Act
      fixture.componentRef.setInput('inputValue', testValue);
      fixture.detectChanges();

      // Assert
      expect(component.control.value).toBe(testValue);
    });

    it('should set control to default value when inputValue is not a number', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const defaultValue = 10;
      fixture.componentRef.setInput('inputDefaultValue', defaultValue);

      // Act
      fixture.componentRef.setInput('inputValue', undefined);
      fixture.detectChanges();

      // Assert
      expect(component.control.value).toBe(defaultValue);
    });

    it('should set control to null when inputValue is not a number and no default', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');

      // Act
      fixture.componentRef.setInput('inputValue', undefined);
      fixture.detectChanges();

      // Assert
      expect(component.control.value).toBeNull();
    });

    it('should not update control when value is the same', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputValue', 100);
      fixture.detectChanges();
      const spy = jest.spyOn(component.control, 'setValue');

      // Act
      fixture.componentRef.setInput('inputValue', 100);
      fixture.detectChanges();

      // Assert
      expect(spy).not.toHaveBeenCalled();
    });
  });

  describe('inputValidation input', () => {
    it('should set validators when inputValidation is provided', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const validations: NumberInputValidation[] = [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
          errorText: 'This field is required',
        },
        {
          errorKey: 'min',
          inputValidator: Validators.min(0),
          errorText: 'Value must be at least 0',
        },
      ];

      // Act
      fixture.componentRef.setInput('inputValidation', validations);
      fixture.detectChanges();

      // Assert
      expect(component.control.hasError('required')).toBeTruthy();
      
      component.control.setValue(-1);
      expect(component.control.hasError('min')).toBeTruthy();
      
      component.control.setValue(1);
      expect(component.control.valid).toBeTruthy();
    });

    it('should clear validators when inputValidation is undefined', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const validations: NumberInputValidation[] = [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
        },
      ];
      fixture.componentRef.setInput('inputValidation', validations);
      fixture.detectChanges();

      // Act
      fixture.componentRef.setInput('inputValidation', undefined);
      fixture.detectChanges();

      // Assert
      expect(component.control.hasError('required')).toBeFalsy();
      expect(component.control.valid).toBeTruthy();
    });

    it('should display validation errors when control is touched and invalid', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const validations: NumberInputValidation[] = [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
          errorText: 'This field is required',
        },
      ];
      fixture.componentRef.setInput('inputValidation', validations);

      // Act
      fixture.detectChanges();
      component.control.markAsTouched();
      fixture.detectChanges();

      // Assert
      const errorElement = fixture.nativeElement.querySelector('[data-what="error"][data-which="required"]');
      expect(errorElement).toBeTruthy();
      expect(errorElement.textContent?.trim()).toBe('This field is required');
    });

    it('should not display validation errors when control is untouched', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const validations: NumberInputValidation[] = [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
          errorText: 'This field is required',
        },
      ];
      fixture.componentRef.setInput('inputValidation', validations);

      // Act
      fixture.detectChanges();

      // Assert
      const errorElement = fixture.nativeElement.querySelector('[data-what="error"]');
      expect(errorElement).toBeFalsy();
    });
  });

  describe('button texts', () => {
    it('should display custom close button text', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const customText = 'Cancel';
      fixture.componentRef.setInput('closeText', customText);

      // Act
      fixture.detectChanges();

      // Assert
      const closeButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="close"]');
      expect(closeButton.textContent?.trim()).toBe(customText);
    });

    it('should display default close button text when not provided', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');

      // Act
      fixture.detectChanges();

      // Assert
      const closeButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="close"]');
      expect(closeButton.textContent?.trim()).toBe('Verlassen');
    });

    it('should display custom confirm button text', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const customText = 'OK';
      fixture.componentRef.setInput('confirmText', customText);

      // Act
      fixture.detectChanges();

      // Assert
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      expect(confirmButton.textContent?.trim()).toBe(customText);
    });

    it('should display default confirm button text when not provided', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');

      // Act
      fixture.detectChanges();

      // Assert
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      expect(confirmButton.textContent?.trim()).toBe('Speichern');
    });
  });

  describe('close functionality', () => {
    it('should emit closed event when close button is clicked', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const closedSpy = jest.fn();
      component.closed.subscribe(closedSpy);
      fixture.detectChanges();

      // Act
      const closeButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="close"]');
      closeButton.click();

      // Assert
      expect(closedSpy).toHaveBeenCalled();
    });
  });

  describe('confirm functionality', () => {
    it('should emit confirmed event with control value when confirm button is clicked and control is valid', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const testValue = 123;
      fixture.componentRef.setInput('inputValue', testValue);
      const confirmedSpy = jest.fn();
      component.confirmed.subscribe(confirmedSpy);
      fixture.detectChanges();

      // Act
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      confirmButton.click();

      // Assert
      expect(confirmedSpy).toHaveBeenCalledWith(testValue);
    });

    it('should emit undefined when control value is null', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      component.control.setValue(null);
      const confirmedSpy = jest.fn();
      component.confirmed.subscribe(confirmedSpy);
      fixture.detectChanges();

      // Act
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      confirmButton.click();

      // Assert
      expect(confirmedSpy).toHaveBeenCalledWith(undefined);
    });

    it('should not emit confirmed event when control is invalid', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputValidation', [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
        },
      ]);
      const confirmedSpy = jest.fn();
      component.confirmed.subscribe(confirmedSpy);
      fixture.detectChanges();

      // Act
      component.confirm();

      // Assert
      expect(confirmedSpy).not.toHaveBeenCalled();
    });

    it('should disable confirm button when control is invalid', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputValidation', [
        {
          errorKey: 'required',
          inputValidator: Validators.required,
        },
      ]);

      // Act
      fixture.detectChanges();

      // Assert
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      expect(confirmButton.disabled).toBeTruthy();
    });

    it('should enable confirm button when control is valid', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      component.control.setValue(42);

      // Act
      fixture.detectChanges();

      // Assert
      const confirmButton: HTMLButtonElement = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      expect(confirmButton.disabled).toBeFalsy();
    });

    it('should confirm on Enter key press in input', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      fixture.componentRef.setInput('inputValue', 42);
      const confirmedSpy = jest.fn();
      component.confirmed.subscribe(confirmedSpy);
      fixture.detectChanges();

      // Act
      const inputElement: HTMLInputElement = fixture.nativeElement.querySelector('[data-what="input"]');
      const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
      inputElement.dispatchEvent(enterEvent);

      // Assert
      expect(confirmedSpy).toHaveBeenCalledWith(42);
    });
  });

  describe('input interactions', () => {
    it('should clear control value when cleared event is triggered', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      component.control.setValue(100);
      fixture.detectChanges();

      // Act
      const inputElement = fixture.debugElement.query(By.css('[data-what="input"]'));
      inputElement.triggerEventHandler('cleared', {});

      // Assert
      expect(component.control.value).toBeNull();
    });

    it('should update validity on blur', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');
      const updateSpy = jest.spyOn(component.control, 'updateValueAndValidity');
      fixture.detectChanges();

      // Act
      const inputElement = fixture.debugElement.query(By.css('[data-what="input"]'));
      inputElement.triggerEventHandler('blur', {});

      // Assert
      expect(updateSpy).toHaveBeenCalled();
    });
  });

  describe('data attributes', () => {
    it('should have correct data attributes on elements', () => {
      // Arrange
      fixture.componentRef.setInput('message', 'Test');

      // Act
      fixture.detectChanges();

      // Assert
      const messageElement = fixture.nativeElement.querySelector('[data-what="message"]');
      expect(messageElement).toBeTruthy();

      const inputElement = fixture.nativeElement.querySelector('[data-what="input"][data-which="number"]');
      expect(inputElement).toBeTruthy();

      const closeButton = fixture.nativeElement.querySelector('[data-what="button"][data-which="close"]');
      expect(closeButton).toBeTruthy();

      const saveButton = fixture.nativeElement.querySelector('[data-what="button"][data-which="save"]');
      expect(saveButton).toBeTruthy();
    });
  });
});