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 'src/components';
import * as WeatherWidgetView from 'src/widgets/WeatherWidgetView';

import {FakeService} from 'tests/__fixtures__/services';
import {SettingsFactory} from 'tests/__fixtures__/settings';
import {WeatherFactory} from 'tests/__fixtures__/weather';

describe('src/widgets/WeatherWidgetView', () => {
  describe('WeatherWidgetSettingsView', () => {
    let fakeNextCharacteristics = null;
    let fakeSettings = null;
    let mockSetNextCharacteristics = null;

    beforeEach(() => {
      fakeNextCharacteristics = {
        city: '',
        units: '',
        apiKey: '',
      };
      fakeSettings = SettingsFactory();
      mockSetNextCharacteristics = jasmine.createSpy();
    });

    it('calls the setNextCharacteristics callback when city input changes', () => {
      // Given
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        city: 'Wroclaw,PL',
      });
    });

    it('calls the setNextCharacteristics callback when units input changes', () => {
      // Given
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

      // Then
      expect(mockSetNextCharacteristics).toHaveBeenCalledWith({
        ...fakeNextCharacteristics,
        units: 'imperial',
      });
    });

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

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

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

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

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

    it('configures and renders the city input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        city: 'Wroclaw,PL',
        units: 'metric',
        apiKey: 'thisisntright',
      };
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

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

    it('configures and renders the units input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        city: 'Wroclaw,PL',
        units: 'metric',
        apiKey: 'thisisntright',
      };
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

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

    it('configures and renders the API key input', () => {
      // Given
      fakeNextCharacteristics = {
        ...fakeNextCharacteristics,
        city: 'Wroclaw,PL',
        units: 'metric',
        apiKey: 'thisisntright',
      };
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

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

    it('does not render the API key input if it is missing in characteristics', () => {
      // Given
      fakeNextCharacteristics = {
        city: 'Wroclaw,PL',
        units: 'metric',
      };
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetSettingsView
          nextCharacteristics={fakeNextCharacteristics}
          settings={fakeSettings}
          setNextCharacteristics={mockSetNextCharacteristics}
        />
      );

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

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

  describe('WeatherWidgetView', () => {
    let fakeAppearance = null;
    let fakeDateTime = null;
    let fakeService = null;
    let fakeServiceState = null;
    let fakeWeather = null;

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

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

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

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

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

    it('configures and renders the Widget', () => {
      // Given
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetView
          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);
      expect(widget.prop('instance')).toEqual(fakeService.instance);
      expect(widget.prop('kind')).toEqual(fakeService.kind);
      expect(widget.prop('layout')).toEqual(fakeService.layout);
    });

    it('renders the loader when state is loading', () => {
      // Given
      spyOn(fakeServiceState, 'isLoading').and.returnValue(true);
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetView
          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(
        <WeatherWidgetView.WeatherWidgetView
          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 weather info when service state has no data', () => {
      // Given
      spyOn(fakeServiceState, 'hasData').and.returnValue(false);
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetView
          appearance={fakeAppearance}
          service={fakeService}
          serviceState={fakeServiceState}
        />
      );

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

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

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

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

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

      const weatherIcon = component.find('Icon[_hint="WeatherIcon"]').at(0);
      expect(weatherIcon.exists()).toBe(true);
      expect(weatherIcon.prop('icon')).toBeDefined();

      const weatherName = component.find('h5[_hint="WeatherName"]').at(0);
      expect(weatherName.exists()).toBe(true);
      expect(weatherName.text()).toEqual(fakeWeather.name);

      const weatherDescription = component.find('h6[_hint="WeatherDescription"]').at(0);
      expect(weatherDescription.exists()).toBe(true);
      expect(weatherDescription.text()).toEqual(
        fakeWeather.weather[0].description
      );

      const weatherTemp = component.find('h5[_hint="WeatherTemp"]').at(0);
      expect(weatherTemp.exists()).toBe(true);
      expect(weatherTemp.text()).toEqual(
        `${Math.round(fakeWeather.main.temp)}°`
      );

      const weatherTempRange = component.find('h6[_hint="WeatherTempRange"]').at(0);
      expect(weatherTempRange.exists()).toBe(true);
      expect(weatherTempRange.text()).toContain(
        `${Math.round(fakeWeather.main.temp_min)}°`
      );
      expect(weatherTempRange.text()).toContain(
        `${Math.round(fakeWeather.main.temp_max)}°`
      );

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

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

    it('does not render FatalError if service state has no fatal error', () => {
      // Given
      const component = shallow(
        <WeatherWidgetView.WeatherWidgetView
          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(
        <WeatherWidgetView.WeatherWidgetView
          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);
    });
  });
});
