import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { DatepickerBase } from './datepicker-base';
import { UI_DATEPICKER_DEFAULT_MIN, UI_DATEPICKER_DEFAULT_MAX } from './tokens';

describe('DatepickerBase', () => {
  // Create a dummy concrete implementation for testing purposes.
  class TestDatepicker extends DatepickerBase<string> {
    parseValue(value: unknown): string | undefined {
      if (typeof value === 'string') {
        return value;
      } else if (value === null || value === undefined) {
        return undefined;
      }
      throw new Error('Invalid value');
    }
  }

  let spectator: SpectatorService<TestDatepicker>;
  const createService = createServiceFactory({
    service: TestDatepicker,
    providers: [
      { provide: UI_DATEPICKER_DEFAULT_MIN, useValue: undefined },
      { provide: UI_DATEPICKER_DEFAULT_MAX, useValue: undefined },
    ],
  });

  beforeEach(() => {
    spectator = createService();
    // Override the model's 'set' method to allow spying.
    spectator.service.value = { set: jest.fn() } as any;
    spectator.service.onChange = jest.fn();
    spectator.service.onTouched = jest.fn();
  });

  describe('setValue', () => {
    it('should call parseValue and set the value', () => {
      spectator.service.setValue('test string');
      expect(spectator.service.value.set).toHaveBeenCalledWith('test string');
    });

    it('should call onChange with parsed value', () => {
      spectator.service.setValue('test string');
      expect(spectator.service.onChange).toHaveBeenCalledWith('test string');
    });

    it('should call onTouched', () => {
      spectator.service.setValue('test string');
      expect(spectator.service.onTouched).toHaveBeenCalled();
    });

    it('should log an error when parseValue throws', () => {
      jest.spyOn(spectator.service, 'parseValue').mockImplementation(() => {
        throw new Error('parse error');
      });
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      spectator.service.setValue({} as any);
      expect(consoleSpy).toHaveBeenCalledWith(
        'Error parsing value:',
        expect.any(Error),
      );
      consoleSpy.mockRestore();
    });
  });

  describe('writeValue', () => {
    it('should call parseValue and set the value', () => {
      spectator.service.writeValue('write test');
      expect(spectator.service.value.set).toHaveBeenCalledWith('write test');
    });

    it('should log an error when parseValue throws', () => {
      jest.spyOn(spectator.service, 'parseValue').mockImplementation(() => {
        throw new Error('write error');
      });
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      spectator.service.writeValue({} as any);
      expect(consoleSpy).toHaveBeenCalledWith(
        'Error parsing value:',
        expect.any(Error),
      );
      consoleSpy.mockRestore();
    });
  });

  describe('registerOnChange', () => {
    it('should register the onChange callback', () => {
      const onChangeMock = jest.fn();
      spectator.service.registerOnChange(onChangeMock);
      expect(spectator.service.onChange).toBe(onChangeMock);
    });
  });

  describe('registerOnTouched', () => {
    it('should register the onTouched callback', () => {
      const onTouchedMock = jest.fn();
      spectator.service.registerOnTouched(onTouchedMock);
      expect(spectator.service.onTouched).toBe(onTouchedMock);
    });
  });
});
