import { Component } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { RangeDatepicker } from './range-datepicker';
import { DateRangeValue } from './types';

// Dummy host component without providers.
@Component({
  template: '',
})
class DummyHostComponent {}

describe('RangeDatepicker Directive', () => {
  let spectator: Spectator<DummyHostComponent>;
  let directive: RangeDatepicker;
  // Provide RangeDatepicker via the factory configuration.
  const createComponent = createComponentFactory({
    component: DummyHostComponent,
    providers: [RangeDatepicker],
  });

  beforeEach(() => {
    // Create the host component and inject RangeDatepicker.
    spectator = createComponent();
    directive = spectator.inject(RangeDatepicker);
  });

  describe('parseValue', () => {
    it('should return a valid date range when provided a valid array', () => {
      // Arrange
      const validRange: DateRangeValue = [
        new Date(2021, 0, 1),
        new Date(2021, 0, 2),
      ];
      // Act
      const result = directive.parseValue(validRange);
      // Assert
      expect(result).toEqual(validRange);
    });

    it('should throw an error when provided an invalid value', () => {
      // Arrange
      const invalidValue = 'invalid';
      // Act & Assert
      expect(() => directive.parseValue(invalidValue)).toThrow();
    });
  });

  describe('sortRangeValuesAsc', () => {
    it('should return the original range when either start or end is missing', () => {
      // Arrange
      const range1: DateRangeValue = [new Date(2021, 0, 1), undefined];
      const range2: DateRangeValue = [undefined, new Date(2021, 0, 2)];
      // Act & Assert
      expect(directive.sortRangeValuesAsc(range1)).toBe(range1);
      expect(directive.sortRangeValuesAsc(range2)).toBe(range2);
    });

    it('should sort the dates in ascending order when end is before start', () => {
      // Arrange
      const start = new Date(2021, 0, 2);
      const end = new Date(2021, 0, 1);
      // Act
      const sorted = directive.sortRangeValuesAsc([start, end]);
      // Assert
      expect(sorted[0]).toEqual(end);
      expect(sorted[1]).toEqual(start);
    });

    it('should return the range unchanged if already in ascending order', () => {
      // Arrange
      const start = new Date(2021, 0, 1);
      const end = new Date(2021, 0, 2);
      // Act
      const sorted = directive.sortRangeValuesAsc([start, end]);
      // Assert
      expect(sorted[0]).toEqual(start);
      expect(sorted[1]).toEqual(end);
    });
  });

  describe('setDateRange', () => {
    let valueMock: jest.Mock, setValueMock: jest.Mock;

    beforeEach(() => {
      valueMock = jest.fn();
      setValueMock = jest.fn();
      // Override the read-only model properties using Object.defineProperty.
      Object.defineProperty(directive, 'value', { value: valueMock });
      Object.defineProperty(directive, 'setValue', { value: setValueMock });
    });

    it('should set a new range with end undefined when current value is fully set', () => {
      // Arrange: current value is fully set.
      const currentRange: DateRangeValue = [
        new Date(2021, 0, 1),
        new Date(2021, 0, 2),
      ];
      valueMock.mockReturnValue(currentRange);
      const newDate = new Date(2021, 0, 3);
      // Act
      directive.setDateRange(newDate);
      // Assert
      expect(setValueMock).toHaveBeenCalledWith([newDate, undefined]);
    });

    it('should set the start date when only end is present', () => {
      // Arrange: only end is present.
      const currentRange: DateRangeValue = [undefined, new Date(2021, 0, 2)];
      valueMock.mockReturnValue(currentRange);
      const newDate = new Date(2021, 0, 3);
      // Act
      directive.setDateRange(newDate);
      const expected = directive.sortRangeValuesAsc([newDate, currentRange[1]]);
      // Assert
      expect(setValueMock).toHaveBeenCalledWith(expected);
    });

    it('should set the end date when only start is present', () => {
      // Arrange: only start is present.
      const currentRange: DateRangeValue = [new Date(2021, 0, 1), undefined];
      valueMock.mockReturnValue(currentRange);
      const newDate = new Date(2021, 0, 3);
      // Act
      directive.setDateRange(newDate);
      const expected = directive.sortRangeValuesAsc([currentRange[0], newDate]);
      // Assert
      expect(setValueMock).toHaveBeenCalledWith(expected);
    });
  });
});
