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

import {IFormicariumService} from 'src/IFormicariumService';
import * as IFormicariumWidgetView from 'src/IFormicariumWidgetView';

import {IFormicariumDataFactory} from 'tests/__fixtures__/iformicarium';

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

    beforeEach(() => {
      fakeNextCharacteristics = {
        mqtt: '',
        name: '',
        topic: '',
      };
      mockSetNextCharacteristics = jasmine.createSpy();
    });

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

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

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

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

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

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        name: 'iformicarium',
      });
    });

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

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

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        topic: 'iformicarium',
      });
    });

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

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

    it('configures and renders the city input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        mqtt: 'thisisntright.local',
        name: 'iformicarium',
        topic: 'iformicarium',
      };
      const component = shallow(
        <IFormicariumWidgetView.IFormicariumWidgetSettingsView
          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.mqtt);
    });

    it('configures and renders the name input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        mqtt: 'thisisntright.local',
        name: 'iformicarium',
        topic: 'iformicarium',
      };
      const component = shallow(
        <IFormicariumWidgetView.IFormicariumWidgetSettingsView
          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.name);
    });

    it('configures and renders the topic input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        mqtt: 'thisisntright.local',
        name: 'iformicarium',
        topic: 'iformicarium',
      };
      const component = shallow(
        <IFormicariumWidgetView.IFormicariumWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

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

  describe('IFormicariumWidgetView', () => {
    let fakeAppearance = null;
    let fakeDateTime = null;
    let fakeService = null;
    let fakeServiceState = null;
    let fakeData = null;

    beforeEach(() => {
      fakeAppearance = {color: 'red'};
      fakeDateTime = DateTime.local(1987, 10, 3, 8, 0, 0);
      fakeService = new IFormicariumService({
        instance: 'testing',
        layout: {x: 0, y: 0, h: 1, w: 1},
      });
      fakeData = IFormicariumDataFactory();
      fakeServiceState = new ServiceState({
        data: fakeData,
      });

      spyOn(DateTime, 'fromSeconds').and.returnValue(fakeDateTime);
      spyOn(fakeDateTime, 'toLocaleString').and.returnValue(
        'Formatted DateTime'
      );
    });

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

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

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

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

      // 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(
        <IFormicariumWidgetView.IFormicariumWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
        />
      );

      // 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(
        <IFormicariumWidgetView.IFormicariumWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
        />
      );

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

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

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

      // When
      const instanceInfo = component.find('div[_hint="InstanceInfo"]').at(0);

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

    it('computes and renders instance info when service state has data', () => {
      // Given
      const component = shallow(
        <IFormicariumWidgetView.IFormicariumWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
        />
      );

      // When
      const instanceInfo = component.find('div[_hint="InstanceInfo"]').at(0);

      // Then
      expect(instanceInfo.exists()).toBe(true);

      const instanceName = component.find('h5[_hint="InstanceName"]').at(0);
      expect(instanceName.exists()).toBe(true);
      expect(instanceName.text()).toEqual(fakeData.name);

      const instanceMetrics = component.find('div[_hint="InstanceMetrics"]').at(0);
      expect(instanceMetrics.exists()).toBe(true);

      const instanceDHTMetric = instanceMetrics.find('div[_hint="InstanceDHTMetric"]').at(0);
      expect(instanceDHTMetric.exists()).toBe(true);
      expect(instanceDHTMetric.find('p').at(0).text()).toEqual(
        fakeData.dht['0'].name
      );
      expect(instanceDHTMetric.find('p').at(1).text()).toContain(
        `Temp: ${fakeData.dht['0'].temp}`
      );
      expect(instanceDHTMetric.find('p').at(2).text()).toEqual(
        `Humi: ${fakeData.dht['0'].humi}%`
      );

      const instanceFanMetric = instanceMetrics.find('div[_hint="InstanceFanMetric"]').at(0);
      expect(instanceFanMetric.exists()).toBe(true);
      expect(instanceFanMetric.find('p').at(1).text()).toEqual('25%');

      const lastUpdate = component.find('small[_hint="LastUpdate"]').at(0);
      expect(lastUpdate.exists()).toBe(true);
      expect(lastUpdate.text()).toContain('Formatted DateTime');

      expect(DateTime.fromSeconds).toHaveBeenCalledWith(fakeData.dt);
      expect(fakeDateTime.toLocaleString).toHaveBeenCalledWith(
        DateTime.TIME_SIMPLE
      );
    });

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

      // 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(
        <IFormicariumWidgetView.IFormicariumWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
        />
      );

      // 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);
    });
  });
});
