import * as ServicesLib from 'src/lib/services';

import {DashboardsFactory} from 'tests/__fixtures__/dashboards';
import {FakeService, FakeWidget} from 'tests/__fixtures__/services';

describe('src/lib/services', () => {
  describe('ServiceState', () => {
    describe('constructor', () => {
      it('initializes the instance with a payload', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(serviceState.payload.data).toEqual({spam: true});
        expect(serviceState.payload.error).toEqual({message: 'FIAL'});
      });

      it('initializes the instance with the default payload', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();

        // Then
        expect(serviceState.payload.data).toBe(null);
        expect(serviceState.payload.error).toBe(null);
      });
    });

    describe('isLoading', () => {
      it('returns true if both data and error fields are null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();

        // Then
        expect(serviceState.isLoading()).toBe(true);
      });

      it('returns false if data is null and error is not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(serviceState.isLoading()).toBe(false);
      });

      it('returns false if data is not null and error is null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
        });

        // Then
        expect(serviceState.isLoading()).toBe(false);
      });

      it('returns false if both data and error fields are not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(serviceState.isLoading()).toBe(false);
      });
    });

    describe('hasData', () => {
      it('returns false if isLoading is true and data is not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
        });
        spyOn(serviceState, 'isLoading').and.returnValue(true);

        // Then
        expect(serviceState.hasData()).toBe(false);
      });

      it('returns false if isLoading is false and data is null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();
        spyOn(serviceState, 'isLoading').and.returnValue(true);

        // Then
        expect(serviceState.hasData()).toBe(false);
      });

      it('returns true if isLoading is false and data is not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
        });
        spyOn(serviceState, 'isLoading').and.returnValue(false);

        // Then
        expect(serviceState.hasData()).toBe(true);
      });
    });

    describe('hasError', () => {
      it('returns false if isLoading is true and error is not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          error: {
            message: 'FIAL',
          },
        });
        spyOn(serviceState, 'isLoading').and.returnValue(true);

        // Then
        expect(serviceState.hasError()).toBe(false);
      });

      it('returns false if isLoading is false and error is null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();
        spyOn(serviceState, 'isLoading').and.returnValue(true);

        // Then
        expect(serviceState.hasError()).toBe(false);
      });

      it('returns true if isLoading is false and error is not null', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          error: {
            message: 'FIAL',
          },
        });
        spyOn(serviceState, 'isLoading').and.returnValue(false);

        // Then
        expect(serviceState.hasError()).toBe(true);
      });
    });

    describe('hasFatalError', () => {
      it('returns true if hasData is false and hasError is true', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();
        spyOn(serviceState, 'hasData').and.returnValue(false);
        spyOn(serviceState, 'hasError').and.returnValue(true);

        // Then
        expect(serviceState.hasFatalError()).toBe(true);
      });

      it('returns false if hasData is true and hasError is true', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();
        spyOn(serviceState, 'hasData').and.returnValue(true);
        spyOn(serviceState, 'hasError').and.returnValue(true);

        // Then
        expect(serviceState.hasFatalError()).toBe(false);
      });

      it('returns false if hasData is true and hasError is false', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();
        spyOn(serviceState, 'hasData').and.returnValue(true);
        spyOn(serviceState, 'hasError').and.returnValue(false);

        // Then
        expect(serviceState.hasFatalError()).toBe(false);
      });
    });

    describe('update', () => {
      it('returns a new ServiceState with updated payload', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState();

        // When
        const result = serviceState.update({
          data: {
            spam: true,
          },
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(result).not.toBe(serviceState);
        expect(result.payload.data).toEqual({spam: true});
        expect(result.payload.error).toEqual({message: 'FIAL'});
        expect(serviceState.payload).toEqual({
          data: null,
          error: null,
        });
      });
    });

    describe('data', () => {
      it('returns the data payload field', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(serviceState.data()).toEqual(serviceState.payload.data);
      });
    });

    describe('error', () => {
      it('returns the error payload field', () => {
        // Given
        const serviceState = new ServicesLib.ServiceState({
          data: {
            spam: true,
          },
          error: {
            message: 'FIAL',
          },
        });

        // Then
        expect(serviceState.error()).toEqual(serviceState.payload.error);
      });
    });
  });

  describe('BaseService', () => {
    let spec = null;

    beforeEach(() => {
      spec = {
        instance: 'fake_instance',
        characteristics: {
          spam: true,
        },
        widgetComponent: FakeWidget,
        layout: {
          x: 0,
          y: 0,
          w: 1,
          h: 1,
        },
      };
    });

    it('includes the subscribable mixin', () => {
      // Given
      const service = new FakeService(spec);

      // Then
      expect(service.__mixins__).toContain('SubscribableMixin');
    });

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

        // Then
        expect(result).toEqual({});
      });
    });

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const service = new FakeService(spec);

        // Then
        expect(service.kind).toEqual(FakeService.kind);
        expect(service.instance).toEqual(spec.instance);
        expect(service.characteristics).toEqual(spec.characteristics);
        expect(service.widget).toEqual(FakeService.widget);
        expect(service.widgetComponent).toEqual(spec.widgetComponent);
        expect(service.layout).toEqual(spec.layout);
      });
    });

    describe('restart', () => {
      it('restarts the service', async () => {
        // Given
        const service = new FakeService(spec);
        spyOn(service, 'notify');
        spyOn(service, 'start').and.resolveTo(null);
        spyOn(service, 'stop').and.resolveTo(null);

        // When
        await service.restart();

        // Then
        expect(service.notify).toHaveBeenCalledWith(null, {reset: true});
        expect(service.stop).toHaveBeenCalledBefore(service.start);
        expect(service.start).toHaveBeenCalled();
      });
    });

    describe('isDummy', () => {
      it('returns false', () => {
        // Given
        const service = new FakeService(spec);

        // Then
        expect(service.isDummy()).toBe(false);
      });
    });

    describe('initialState', () => {
      it('returns null', () => {
        // Given
        const service = new FakeService(spec);

        // Then
        expect(service.initialState()).toBe(null);
      });
    });

    describe('setCharacteristics', () => {
      it('sets the new characteristics', () => {
        // Given
        const service = new FakeService(spec);

        const newCharacteristics = {spam: false};

        // When
        service.setCharacteristics(newCharacteristics);

        // Then
        expect(service.characteristics).not.toBe(newCharacteristics);
        expect(service.characteristics).toEqual(newCharacteristics);
      });
    });

    describe('setLayout', () => {
      it('sets the new layout', () => {
        // Given
        const service = new FakeService(spec);

        const newLayout = {x: 1, y: 1, w: 2, h: 2};

        // When
        service.setLayout(newLayout);

        // Then
        expect(service.layout).not.toBe(newLayout);
        expect(service.layout).toEqual(newLayout);
      });
    });

    describe('toJSON', () => {
      it('returns a JSON-serializable representation of the service', () => {
        // Given
        const service = new FakeService(spec);

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

        // Then
        expect(result).toEqual({
          kind: service.kind,
          instance: service.instance,
          characteristics: service.characteristics,
          layout: service.layout,
        });
      });
    });
  });

  describe('DummyService', () => {
    let spec = null;

    beforeEach(() => {
      spec = {
        instance: 'fake_instance',
        characteristics: {
          spam: true,
        },
        widgetComponent: FakeWidget,
        layout: {
          x: 0,
          y: 0,
          w: 1,
          h: 1,
        },
      };
    });

    describe('constructor', () => {
      it('initializes the instance', () => {
        // Given
        const service = new ServicesLib.DummyService(spec);

        // Then
        expect(service.instance).toBe(null);
        expect(service.widgetComponent).toBe(null);
      });
    });

    describe('isDummy', () => {
      it('returns true', () => {
        // Given
        const service = new ServicesLib.DummyService(spec);

        // Then
        expect(service.isDummy()).toBe(true);
      });
    });
  });

  describe('lookupService', () => {
    let dashboards = null;

    beforeEach(() => {
      dashboards = DashboardsFactory();
    });

    it('returns DummyService instance if the specified kind does not exist', () => {
      // Given
      const result = ServicesLib.lookupService(
        dashboards, 'Testing', 'fake_instance'
      );

      // Then
      expect(result).toBeInstanceOf(ServicesLib.DummyService);
      expect(result.isDummy()).toBe(true);
    });

    it('returns DummyService instance if the specified instance does not exist', () => {
      // Given
      const result = ServicesLib.lookupService(
        dashboards, 'FakeService', 'testing'
      );

      // Then
      expect(result).toBeInstanceOf(ServicesLib.DummyService);
      expect(result.isDummy()).toBe(true);
    });

    it('returns the service that matches the specified kind and instace', () => {
      // Given
      const result = ServicesLib.lookupService(
        dashboards, 'FakeService', 'fake_instance'
      );

      // Then
      expect(result).toBe(dashboards[0].services[0]);
    });
  });

  describe('lookupServices', () => {
    let dashboards = null;

    beforeEach(() => {
      dashboards = DashboardsFactory();
      dashboards[0].services.push(new ServicesLib.DummyService());
      dashboards[0].services.push(new FakeService({
        instance: 'fake_instance2',
      }));
    });

    it('returns all instances of services specified by kind', () => {
      // Givem
      const result = ServicesLib.lookupServices(dashboards, 'FakeService');

      // Then
      expect(result.length).toEqual(3);
      expect(result[0]).toBe(dashboards[0].services[0]);
      expect(result[1]).toBe(dashboards[0].services[1]);
      expect(result[2]).toBe(dashboards[0].services[3]);
    });
  });
});
