import {FatalError, Widget} from '@bthlabs/homehub-components';
import {ServiceState} from '@bthlabs/homehub-core';
import {shallow} from 'enzyme';
import React from 'react';
import {Form} from 'react-bootstrap';

import {TradfriService} from 'src/TradfriService';
import * as TradfriWidgetView from 'src/TradfriWidgetView';

import {TradfriDataFactory} from 'tests/__fixtures__/tradfri';

describe('src/widgets/TradfriWidgetView', () => {
  describe('TradfriWidgetSettingsView', () => {
    let fakeNextCharacteristics = null;
    let mockSetNextCharacteristics = null;

    beforeEach(() => {
      fakeNextCharacteristics = {
        host: '',
        key: '',
      };
      mockSetNextCharacteristics = jasmine.createSpy();
    });

    it('calls the setNextCharacteristics callback when host input changes', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

      // When
      component.find(Form.Control).at(0).simulate('change', {
        target: {value: 'thisisntright.local'},
      });

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        host: 'thisisntright.local',
      });
    });

    it('calls the setNextCharacteristics callback when key input changes', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

      // When
      component.find(Form.Control).at(1).simulate('change', {
        target: {value: 'thisisntright'},
      });

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        key: 'thisisntright',
      });
    });

    it('renders empty when nextCharacteristics is empty', () => {
      // Given
      fakeNextCharacteristics = null;
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

      // Then
      expect(component.isEmptyRender()).toBe(true);
    });

    it('configures and renders the host input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        host: 'thisisntright.local',
        key: 'thisisntright',
      };
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

      // When
      const input = component.find(Form.Control).at(0);

      // Then
      expect(input.exists()).toBe(true);
      expect(input.prop('value')).toEqual(fakeNextCharacteristics.host);
    });

    it('configures and renders the key input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        host: 'thisisntright.local',
        key: 'thisisntright',
      };
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

      // When
      const input = component.find(Form.Control).at(1);

      // Then
      expect(input.exists()).toBe(true);
      expect(input.prop('value')).toEqual(fakeNextCharacteristics.key);
    });
  });

  describe('Group', () => {
    let fakeData = null;
    let mockOnSetLightsState = null;

    beforeEach(() => {
      fakeData = TradfriDataFactory();
      mockOnSetLightsState = jasmine.createSpy();
    });

    it('calls the onSetLightsState callback when it is clicked', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.Group
          group={fakeData.groups[0]}
          lights={fakeData.lights}
          onSetLightsState={mockOnSetLightsState}
        />
      );

      // When
      component.simulate('click');

      // Then
      expect(mockOnSetLightsState).toHaveBeenCalledWith(
        ['light1', 'light2'], false
      );
    });

    it('renders as on when all the lights in the the group are on', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.Group
          group={fakeData.groups[0]}
          lights={fakeData.lights}
          onSetLightsState={mockOnSetLightsState}
        />
      );

      // Then
      expect(component.hasClass('group-on')).toBe(true);
    });

    it('does not render as of when some of the lights in the the group are off', () => {
      // Given
      fakeData.lights[0].state = false;
      const component = shallow(
        <TradfriWidgetView.Group
          group={fakeData.groups[0]}
          lights={fakeData.lights}
          onSetLightsState={mockOnSetLightsState}
        />
      );

      // Then
      expect(component.hasClass('group-on')).toBe(false);
    });

    it('renders the group name', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.Group
          group={fakeData.groups[0]}
          lights={fakeData.lights}
          onSetLightsState={mockOnSetLightsState}
        />
      );

      // When
      const groupName = component.find('span[_hint="GroupName"]').at(0);

      // Then
      expect(groupName.exists()).toBe(true);
      expect(groupName.text()).toEqual(fakeData.groups[0].name);
    });
  });

  describe('TradfriWidgetView', () => {
    let fakeAppearance = null;
    let fakeService = null;
    let fakeServiceState = null;
    let fakeData = null;
    let mockSetServiceState = null;

    beforeEach(() => {
      fakeAppearance = {color: 'red'};
      fakeService = new TradfriService({
        instance: 'testing',
        layout: {x: 0, y: 0, h: 1, w: 1},
      });
      fakeData = TradfriDataFactory();
      fakeServiceState = new ServiceState({
        data: fakeData,
      });
      mockSetServiceState = jasmine.createSpy();

      spyOn(fakeService, 'setLightsState').and.resolveTo('ok');
    });

    it('defines the widget attributes', () => {
      // Then
      expect(TradfriWidgetView.TradfriWidgetView.defaultLayout).toEqual({
        h: jasmine.any(Number),
        w: jasmine.any(Number),
      });
      expect(TradfriWidgetView.TradfriWidgetView.icon).toBeDefined();
      expect(TradfriWidgetView.TradfriWidgetView.layoutConstraints).toEqual({
        minH: jasmine.any(Number),
        minW: jasmine.any(Number),
      });
      expect(TradfriWidgetView.TradfriWidgetView.settingsView).toEqual(
        TradfriWidgetView.TradfriWidgetSettingsView
      );
      expect(TradfriWidgetView.TradfriWidgetView.title).toEqual('Tradfri');
    });

    it('handles group lights state change', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      component.find(TradfriWidgetView.Group).at(0).invoke('onSetLightsState')(
        ['light1'], false
      );

      // Then
      expect(fakeService.setLightsState).toHaveBeenCalledWith(
        ['light1'], false
      );

      const expectedNewState = {data: {...fakeData}};
      expectedNewState.data.lights[0].state = false;
      expect(mockSetServiceState).toHaveBeenCalledWith(expectedNewState);
    });

    it('renders empty when service state is null', () => {
      // Given
      fakeServiceState = null;
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // Then
      expect(component.isEmptyRender()).toBe(true);
    });

    it('configures and renders the Widget', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const widget = component.find(Widget).at(0);

      // Then
      expect(widget.exists()).toBe(true);
      expect(widget.prop('appearance')).toEqual(fakeAppearance);
    });

    it('renders the loader when state is loading', () => {
      // Given
      spyOn(fakeServiceState, 'isLoading').and.returnValue(true);
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const loader = component.find('Icon[_hint="Loader"]').at(0);

      // Then
      expect(loader.exists()).toBe(true);
      expect(loader.prop('icon')).toBeDefined();
    });

    it('does not render the loader when state is not loading', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const loader = component.find('Icon[_hint="Loader"]').at(0);

      // Then
      expect(loader.exists()).toBe(false);
    });

    it('does not compute and render groups when service state has no data', () => {
      // Given
      spyOn(fakeServiceState, 'hasData').and.returnValue(false);
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const groups = component.find(TradfriWidgetView.Group);

      // Then
      expect(groups.length).toEqual(0);
    });

    it('configures and renders a Group for every group', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const groups = component.find(TradfriWidgetView.Group);

      // Then
      expect(groups.length).toEqual(1);

      const group = groups.at(0);
      expect(group.prop('group')).toEqual(fakeData.groups[0]);
      expect(group.prop('lights')).toEqual(fakeData.lights);
    });

    it('does not render FatalError if service state has no fatal error', () => {
      // Given
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const fatalError = component.find(FatalError).at(0);

      // Then
      expect(fatalError.exists()).toBe(false);
    });

    it('configures and renders FatalError if service state has fatal error', () => {
      // Given
      spyOn(fakeServiceState, 'hasFatalError').and.returnValue(true);
      const component = shallow(
        <TradfriWidgetView.TradfriWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
          setServiceState={mockSetServiceState}
        />
      );

      // When
      const fatalError = component.find(FatalError).at(0);

      // Then
      expect(fatalError.exists()).toBe(true);
      expect(fatalError.prop('service')).toEqual(fakeService);
      expect(fatalError.prop('serviceState')).toEqual(fakeServiceState);
    });
  });
});
