/* eslint-disable no-unused-vars */
import {shallow} from 'enzyme';
import React from 'react';
import {withContext} from 'shallow-with-context';

import {ServiceContainer} from 'src/containers/ServiceContainer';
import {DEFAULT_DASHBOARDS_CONTEXT} from 'src/context/DashboardsContext';
import {DummyService, ServiceState} from 'src/lib/services';

import {DashboardsFactory} from 'tests/__fixtures__/dashboards';

describe('src/containers/ServiceContainer', () => {
  const ServiceContainerWithContext = withContext(
    ServiceContainer, DEFAULT_DASHBOARDS_CONTEXT
  );

  let context = null;

  beforeEach(() => {
    context = {
      ...DEFAULT_DASHBOARDS_CONTEXT,
      nukeService: jasmine.createSpy(),
      saveServiceCharacteristics: jasmine.createSpy(),
      saveServiceLayout: jasmine.createSpy(),
      addService: jasmine.createSpy(),
      setCurrentDashboardId: jasmine.createSpy(),
      addDashboard: jasmine.createSpy(),
      dashboards: DashboardsFactory(),
    };
  });

  describe('constructor', () => {
    it('initializes the state', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // Then
      expect(component.state('serviceState')).toBe(null);
      expect(component.state('showSettingsModal')).toBe(false);
      expect(component.state('nextCharacteristics')).toBe(null);
    });
  });

  describe('service', () => {
    it('looks up the bound service instance', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // When
      const service = component.instance().service();

      // Then
      expect(service).toEqual(context.dashboards[0].services[0]);
    });
  });

  describe('setServiceState', () => {
    it('initializes new state object if the current state is `null`', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // When
      component.instance().setServiceState({data: {spam: true}}, {});

      // Then
      expect(component.state('serviceState')).toBeInstanceOf(ServiceState);
      expect(component.state('serviceState').data()).toEqual({spam: true});
    });

    it('initializes new state object if reset is requested', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const oldState = new ServiceState({data: {spam: false}});
      component.setState({serviceState: oldState});

      // When
      component.instance().setServiceState(
        {data: {spam: true}}, {reset: true}
      );

      // Then
      expect(component.state('serviceState')).not.toBe(oldState);
    });

    it('updates the existing state object with new payload', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const oldState = new ServiceState({data: {spam: false}});
      component.setState({serviceState: oldState});

      // When
      component.instance().setServiceState({data: {spam: true}}, {});

      // Then
      expect(component.state('serviceState')).not.toBe(oldState);
      expect(component.state('serviceState').data()).toEqual({spam: true});
    });
  });

  it('allows setting next characteristics', () => {
    // Given
    const component = shallow(
      <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
        {(props) => <span>It works!</span>}
      </ServiceContainerWithContext>,
      {
        context: context,
      }
    );

    // When
    component.instance().setNextCharacteristics({spam: true});

    // Then
    expect(component.state('nextCharacteristics')).toEqual({spam: true});
  });

  describe('setNextCharacteristicsFromService', () => {
    it('resets the next characteristics if the service is null', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      component.setState({nextCharacteristics: {spam: false}});

      // When
      component.instance().setNextCharacteristicsFromService(null);

      // Then
      expect(component.state('nextCharacteristics')).toBe(null);
    });

    it('resets the next characteristics if the service has no widget', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      component.setState({nextCharacteristics: {spam: false}});

      const service = context.dashboards[0].services[0];
      service.widgetComponent = null;

      // When
      component.instance().setNextCharacteristicsFromService(service);

      // Then
      expect(component.state('nextCharacteristics')).toBe(null);
    });

    it('resets the next characteristics if the service is dummy', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      component.setState({nextCharacteristics: {spam: false}});

      const service = new DummyService();

      // When
      component.instance().setNextCharacteristicsFromService(service);

      // Then
      expect(component.state('nextCharacteristics')).toBe(null);
    });

    it('sets the next characteristics from the service characteristics', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const service = context.dashboards[0].services[0];

      // When
      component.instance().setNextCharacteristicsFromService(service);

      // Then
      expect(component.state('nextCharacteristics')).toEqual(
        service.characteristics
      );
    });
  });

  describe('showHideSettingsModal', () => {
    it('sets next characteristics from the service when modal is to be shown', () => {
      // Givem
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setNextCharacteristicsFromService');

      // When
      component.instance().showHideSettingsModal(true);

      // Then
      expect(component.instance().setNextCharacteristicsFromService).toHaveBeenCalledWith(
        context.dashboards[0].services[0]
      );
    });

    it('resets next characteristics when modal is to be hidden', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setNextCharacteristicsFromService');

      // When
      component.instance().showHideSettingsModal(false);

      // Then
      expect(component.instance().setNextCharacteristicsFromService).toHaveBeenCalledWith(
        null
      );
    });

    it('updates the state with new visibility flag', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setNextCharacteristicsFromService');

      // When
      component.instance().showHideSettingsModal(true);

      // Then
      expect(component.state('showSettingsModal')).toBe(true);
    });
  });

  describe('onSettingsButtonClick', () => {
    it('requests the settings modal to be shown', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'showHideSettingsModal');

      // When
      component.instance().onSettingsButtonClick();

      // Then
      expect(component.instance().showHideSettingsModal).toHaveBeenCalledWith(
        true
      );
    });
  });

  describe('onSettingsModalClose', () => {
    it('requests the settings modal to be hidden', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'showHideSettingsModal');

      // When
      component.instance().onSettingsModalClose();

      // Then
      expect(component.instance().showHideSettingsModal).toHaveBeenCalledWith(
        false
      );
    });
  });

  describe('onSettingsModalSaveButtonClick', () => {
    it('requests the next characteristics to be saved', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      component.setState({nextCharacteristics: {spam: true}});

      // When
      component.instance().onSettingsModalSaveButtonClick();

      // Then
      expect(context.saveServiceCharacteristics).toHaveBeenCalledWith(
        'FakeService', 'fake_instance', {spam: true}
      );
    });

    it('closes the settings modal', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'onSettingsModalClose');

      // When
      component.instance().onSettingsModalSaveButtonClick();

      // Then
      expect(component.instance().onSettingsModalClose).toHaveBeenCalled();
    });
  });

  describe('onSettingsModalNukeButtonClick', () => {
    it('requests the service to be nuked', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // When
      component.instance().onSettingsModalNukeButtonClick();

      // Then
      expect(context.nukeService).toHaveBeenCalledWith(
        'FakeService', 'fake_instance'
      );
    });

    it('closes the settings modal', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'onSettingsModalClose');

      // When
      component.instance().onSettingsModalNukeButtonClick();

      // Then
      expect(component.instance().onSettingsModalClose).toHaveBeenCalled();
    });
  });

  describe('onAppearancePopupColorChange', () => {
    it('requests the updated characteristics to be saved', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // When
      component.instance().onAppearancePopupColorChange('red');

      // Then
      expect(context.saveServiceCharacteristics).toHaveBeenCalledWith(
        'FakeService', 'fake_instance', {
          spam: true,
          appearance: {
            color: 'red',
          },
        }
      );
    });
  });

  describe('onServiceRestartButtonClick', () => {
    it('requests the updated characteristics to be saved', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const service = context.dashboards[0].services[0];
      spyOn(service, 'restart');

      // When
      component.instance().onServiceRestartButtonClick();

      // Then
      expect(service.restart).toHaveBeenCalled();
    });
  });

  describe('unsubscribeIfNeeded', () => {
    it('calls the unsubscribe callback', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      component.instance().serviceUnsubscribe = jasmine.createSpy();

      // When
      component.instance().unsubscribeIfNeeded();

      // Then
      expect(component.instance().serviceUnsubscribe).toHaveBeenCalled();
    });
  });

  describe('componentDidMount', () => {
    it('is a noop if the service is null', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setServiceState');
      spyOn(component.instance(), 'unsubscribeIfNeeded');
      spyOn(component.instance(), 'service').and.returnValue(null);

      // When
      component.instance().componentDidMount();

      // Then
      expect(component.instance().setServiceState).not.toHaveBeenCalled();
      expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
      expect(component.instance().serviceUnsubscribe).toBe(null);
    });

    it('is a noop if the service has no widget', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setServiceState');
      spyOn(component.instance(), 'unsubscribeIfNeeded');

      context.dashboards[0].services[0].widgetComponent = null;

      // When
      component.instance().componentDidMount();

      // Then
      expect(component.instance().setServiceState).not.toHaveBeenCalled();
      expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
      expect(component.instance().serviceUnsubscribe).toBe(null);
    });

    it('is a noop if the service is dummy', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setServiceState');
      spyOn(component.instance(), 'unsubscribeIfNeeded');
      spyOn(component.instance(), 'service').and.returnValue(
        new DummyService()
      );

      // When
      component.instance().componentDidMount();

      // Then
      expect(component.instance().setServiceState).not.toHaveBeenCalled();
      expect(component.instance().unsubscribeIfNeeded).not.toHaveBeenCalled();
      expect(component.instance().serviceUnsubscribe).toBe(null);
    });

    it('sets itself up for the service', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );
      spyOn(component.instance(), 'setServiceState');
      spyOn(component.instance(), 'unsubscribeIfNeeded');

      const service = context.dashboards[0].services[0];
      spyOn(service, 'start');

      const initialState = new ServiceState({data: {initial: true}});
      spyOn(service, 'initialState').and.returnValue(initialState);

      const fakeUnsubscribe = jasmine.createSpy();
      spyOn(service, 'subscribe').and.returnValue(fakeUnsubscribe);

      // When
      component.instance().componentDidMount();

      // Then
      expect(component.instance().setServiceState).toHaveBeenCalledWith(
        initialState, true
      );
      expect(service.start).toHaveBeenCalled();
      expect(component.instance().unsubscribeIfNeeded).toHaveBeenCalled();
      expect(service.subscribe).toHaveBeenCalledWith(
        component.instance().setServiceState
      );
      expect(component.instance().serviceUnsubscribe).toEqual(fakeUnsubscribe);
    });
  });

  describe('componentWillUnmount', () => {
    it('unsubscribes from the service', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {(props) => <span>It works!</span>}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const mockUnsubscribeIfNeeded = jasmine.createSpy();
      component.instance().unsubscribeIfNeeded = mockUnsubscribeIfNeeded;

      // When
      component.unmount();

      // Then
      expect(mockUnsubscribeIfNeeded).toHaveBeenCalled();
    });
  });

  describe('render', () => {
    let childrenFunc = null;

    beforeEach(() => {
      childrenFunc = jasmine.createSpy();
      childrenFunc.returnValue = <span>It works!</span>;
    });

    it('calls the children func with props', () => {
      // Given
      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {childrenFunc}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      const serviceState = new ServiceState({data: {spam: true}});
      component.setState({
        serviceState: serviceState,
        showSettingsModal: true,
        nextCharacteristics: {eggs: true},
      });

      // Then
      expect(childrenFunc).toHaveBeenCalledWith({
        hasSettingsView: true,
        service: context.dashboards[0].services[0],
        serviceState: serviceState,
        setServiceState: component.instance().setServiceState,
        settingsViewProps: {
          kind: 'FakeService',
          instance: 'fake_instance',
          nextCharacteristics: {eggs: true},
          setNextCharacteristics: component.instance().setNextCharacteristics,
        },
        showSettingsModal: true,
        onAppearancePopupColorChange: component.instance().onAppearancePopupColorChange,
        onServiceRestartButtonClick: component.instance().onServiceRestartButtonClick,
        onSettingsButtonClick: component.instance().onSettingsButtonClick,
        onSettingsModalClose: component.instance().onSettingsModalClose,
        onSettingsModalNukeButtonClick: component.instance().onSettingsModalNukeButtonClick,
        onSettingsModalSaveButtonClick: component.instance().onSettingsModalSaveButtonClick,
      });
    });

    it('disables settings view if the service is dummy', () => {
      // Given
      const service = context.dashboards[0].services[0];
      spyOn(service, 'isDummy').and.returnValue(true);

      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {childrenFunc}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // Then
      expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
        hasSettingsView: false,
      }));
    });

    it('disables settings view if the service has no widget', () => {
      // Given
      const service = context.dashboards[0].services[0];
      service.widgetComponent = null;

      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {childrenFunc}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // Then
      expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
        hasSettingsView: false,
      }));
    });

    it('disables settings view if the service widget has no settings view', () => {
      // Given
      const service = context.dashboards[0].services[0];
      const oldSettingsView = service.widgetComponent.settingsView;
      service.widgetComponent.settingsView = undefined;

      const component = shallow(
        <ServiceContainerWithContext kind="FakeService" instance="fake_instance">
          {childrenFunc}
        </ServiceContainerWithContext>,
        {
          context: context,
        }
      );

      // Then
      expect(childrenFunc).toHaveBeenCalledWith(jasmine.objectContaining({
        hasSettingsView: false,
      }));

      // After
      service.widgetComponent.settingsView = oldSettingsView;
    });
  });
});
