import {DateTime} from 'luxon';

import {Clock} from 'src/lib/clock';
import * as TimeService from 'src/services/time';

describe('src/services/time', () => {
  describe('TimeService', () => {
    let fakeClock = null;

    beforeEach(() => {
      fakeClock = jasmine.createSpyObj('FakeClock', [
        'addEventListener', 'removeEventListener',
      ]);
      fakeClock.now = DateTime.local();
      spyOn(Clock, 'instance').and.returnValue(fakeClock);
    });

    describe('emptyCharacteristics', () => {
      it('returns empty characteristics', () => {
        // Given
        const result = TimeService.TimeService.emptyCharacteristics();

        // Then
        expect(result).toEqual({
          locale: '',
          timeFormat: '',
          dateFormat: 'DATE_HUGE',
        });
      });
    });

    describe('payloadFromDateTime', () => {
      let now = null;

      beforeEach(() => {
        now = DateTime.local(1985, 10, 3, 8, 0, 0);
        spyOn(now, 'setLocale').and.callFake(() => {
          return now;
        });
        spyOn(now, 'toFormat').and.returnValue('Formatted Time String');

        spyOn(now, 'toLocaleString');
        now.toLocaleString.withArgs(DateTime.TIME_SIMPLE).and.returnValue(
          'Simple Time String'
        );
        now.toLocaleString.withArgs(DateTime.DATE_HUGE).and.returnValue(
          'Huge Date String'
        );
        now.toLocaleString.withArgs(DateTime.DATE_FULL).and.returnValue(
          'Full Date String'
        );
      });

      it('sets locale if it is specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        characteristics.locale = 'en-us';
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        service.payloadFromDateTime(now);

        // Then
        expect(now.setLocale).toHaveBeenCalledWith('en-us');
      });

      it('does not set locale if it is not specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        service.payloadFromDateTime(now);

        // Then
        expect(now.setLocale).not.toHaveBeenCalled();
      });

      it('uses timeFormat to format the time string if it is specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        characteristics.timeFormat = 'hh:mm a';
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        const result = service.payloadFromDateTime(now);

        // Then
        expect(result.data.time).toEqual('Formatted Time String');
        expect(now.toFormat).toHaveBeenCalledWith('hh:mm a');
        expect(now.toLocaleString).not.toHaveBeenCalledWith(
          DateTime.TIME_SIMPLE
        );
      });

      it('uses default time format to format the time string if timeFormat is not specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        const result = service.payloadFromDateTime(now);

        // Then
        expect(result.data.time).toEqual('Simple Time String');
        expect(now.toFormat).not.toHaveBeenCalled();
        expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.TIME_SIMPLE);
      });

      it('uses default date format to format the date string if dateFormat is not specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        characteristics.dateFormat = '';
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        const result = service.payloadFromDateTime(now);

        // Then
        expect(result.data.date).toEqual('Huge Date String');
        expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.DATE_HUGE);
      });

      it('uses dateFormat to format the date string if it is specified', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        characteristics.dateFormat = 'DATE_FULL';
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        const result = service.payloadFromDateTime(now);

        // Then
        expect(result.data.date).toEqual('Full Date String');
        expect(now.toLocaleString).toHaveBeenCalledWith(DateTime.DATE_FULL);
      });
    });

    describe('onClockTick', () => {
      it('notfies subscribers with payload', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const fakePayload = {
          data: {
            date: '1987-10-03',
            time: '08:00 AM',
          },
        };
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });
        spyOn(service, 'payloadFromDateTime').and.returnValue(fakePayload);
        spyOn(service, 'notify');

        // When
        service.onClockTick(fakeClock);

        // Then
        expect(service.notify).toHaveBeenCalledWith(fakePayload);
        expect(service.payloadFromDateTime).toHaveBeenCalledWith(
          fakeClock.now
        );
      });
    });

    describe('initialState', () => {
      it('returns payload from the current timestamp', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const fakePayload = {
          data: {
            date: '1987-10-03',
            time: '08:00 AM',
          },
        };
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });
        spyOn(service, 'payloadFromDateTime').and.returnValue(fakePayload);

        // When
        const result = service.initialState();

        // Then
        expect(result).toEqual(fakePayload);
        expect(service.payloadFromDateTime).toHaveBeenCalledWith(
          fakeClock.now
        );
      });
    });

    describe('setCharacteristics', () => {
      it('triggers the onClockTick to update', () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const newCharacteristics = {
          ...characteristics,
          locale: 'en-us',
        };
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });
        spyOn(service, 'onClockTick');

        // When
        service.setCharacteristics(newCharacteristics);

        // Then
        expect(service.characteristics).toEqual(newCharacteristics);
        expect(service.onClockTick).toHaveBeenCalledWith(fakeClock);
      });
    });

    describe('start', () => {
      it('adds event listener for the clock tick event', async () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        await service.start();

        // Then
        expect(fakeClock.addEventListener).toHaveBeenCalledWith(
          'tick', service.onClockTick
        );
      });
    });

    describe('start', () => {
      it('removes event listener for the clock tick event', async () => {
        // Given
        const characteristics = TimeService.TimeService.emptyCharacteristics();
        const service = new TimeService.TimeService({
          instance: 'testing',
          characteristics: characteristics,
        });

        // When
        await service.stop();

        // Then
        expect(fakeClock.removeEventListener).toHaveBeenCalledWith(
          'tick', service.onClockTick
        );
      });
    });
  });
});
