# -*- coding: utf-8 -*-
from unittest import mock

import aiohttp
import aiojobs

from homehub_backend.lib.services import ServiceData
from homehub_backend.services import weather


class Test_WeatherService:
    def _valid_characteristics(self):
        return {
            'city': 'Wroclaw,PL',
            'units': 'metric',
        }

    def test_init(self, homehub_app):
        service = weather.WeatherService(
            homehub_app, 'testing', self._valid_characteristics(),
        )
        assert service._current_data is None
        assert service.api_key is None
        assert service.job is None
        assert service.session is None

    async def test_update_weather(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.api_key = 'api_key'
        service.session = mock.Mock(spec=aiohttp.ClientSession)

        fake_data = {'spam': True}
        fake_response = mock.Mock(spec=aiohttp.ClientResponse)
        fake_response.status = 200
        fake_response.json.return_value = fake_data
        service.session.get = mock.AsyncMock()
        service.session.get.return_value = fake_response

        await service.update_weather()
        assert service._current_data.data == fake_data
        assert service._current_data.exception is None

        service.session.get.assert_called_with(service.API_URL, params={
            'APPID': 'api_key',
            'q': characteristics['city'],
            'units': characteristics['units'],
        })
        assert fake_response.release.called is True

    async def test_update_weather_non_ok_response(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.api_key = 'api_key'
        service.session = mock.Mock(spec=aiohttp.ClientSession)

        fake_response = mock.Mock(spec=aiohttp.ClientResponse)
        fake_response.status = 400
        fake_response.reason = 'Bad Request'
        fake_response.json.return_value = None
        service.session.get = mock.AsyncMock()
        service.session.get.return_value = fake_response

        await service.update_weather()
        assert service._current_data.data is None
        assert service._current_data.exception is not None
        assert isinstance(service._current_data.exception, RuntimeError) is True
        assert service._current_data.exception.args[0] == 'HTTP 400 Bad Request'

        assert fake_response.release.called is True

    async def test_update_weather_request_exception(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.api_key = 'api_key'
        service.session = mock.Mock(spec=aiohttp.ClientSession)

        service.session.get = mock.AsyncMock()
        exception = RuntimeError('FIAL')
        service.session.get.side_effect = exception

        await service.update_weather()
        assert service._current_data.data is None
        assert service._current_data.exception == exception

    async def test_current_data(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service._current_data = ServiceData(data='spam')

        result = await service.current_data()
        assert result is service._current_data

    async def test_current_data_empty(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )

        result = await service.current_data()
        assert result != service._current_data
        assert result.data is None
        assert result.exception is None

    async def test_worker(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.job = mock.Mock(spec=aiojobs._job.Job)
        service.job.closed = True
        service.update_weather = mock.AsyncMock()
        service.notify = mock.AsyncMock()

        with mock.patch('asyncio.sleep') as mock_asyncio_sleep:
            await service.worker()

            mock_asyncio_sleep.assert_called_with(600)
            assert service.update_weather.called is True
            assert service.notify.called is True

    async def test_start(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.update_weather = mock.AsyncMock()

        fake_worker = mock.AsyncMock()
        service.worker = mock.Mock()
        service.worker.return_value = fake_worker

        with mock.patch('aiojobs.aiohttp.get_scheduler_from_app') as mock_get_scheduler:
            with mock.patch('aiohttp.ClientSession') as mock_session:
                fake_scheduler = mock.Mock(spec=aiojobs._scheduler.Scheduler)
                fake_scheduler.spawn.return_value = mock.Mock(
                    spec=aiojobs._job.Job,
                )
                mock_get_scheduler.return_value = fake_scheduler

                fake_session = mock.Mock(spec=aiohttp.ClientSession)
                mock_session.return_value = fake_session

                await service.start()
                assert service.api_key == homehub_app['SETTINGS'].WEATHER_SERVICE_API_KEY
                assert service.session == fake_session
                assert service.update_weather.called is True
                mock_get_scheduler.assert_called_with(homehub_app)
                fake_scheduler.spawn.assert_called_with(fake_worker)

    async def test_start_already_started(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.update_weather = mock.AsyncMock()

        fake_worker = mock.AsyncMock()
        service.worker = mock.Mock()
        service.worker.return_value = fake_worker

        with mock.patch('aiojobs.aiohttp.get_scheduler_from_app') as mock_get_scheduler:
            with mock.patch('aiohttp.ClientSession') as mock_session:
                fake_scheduler = mock.Mock(spec=aiojobs._scheduler.Scheduler)
                mock_get_scheduler.return_value = fake_scheduler

                fake_session = mock.Mock(spec=aiohttp.ClientSession)
                mock_session.return_value = fake_session

                service._current_data = ServiceData(data='spam')
                service.session = fake_session
                service.job = mock.Mock(spec=aiojobs._job.Job)

                await service.start()
                assert not mock_session.called
                assert not service.update_weather.called
                assert not mock_get_scheduler.called
                assert not fake_scheduler.spawn.called

    async def test_stop(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.session = mock.Mock(spec=aiohttp.ClientSession)
        service.session.closed = False
        service.job = mock.Mock(spec=aiojobs._job.Job)
        service.job.closed = False

        await service.stop()
        assert service.session.close.called is True
        assert service.job.close.called is True

    async def test_stop_already_stopped(self, homehub_app):
        characteristics = self._valid_characteristics()
        service = weather.WeatherService(
            homehub_app, 'testing', characteristics,
        )
        service.session = mock.Mock(spec=aiohttp.ClientSession)
        service.session.closed = True
        service.job = mock.Mock(spec=aiojobs._job.Job)
        service.job.closed = True

        await service.stop()
        assert service.session.close.called is False
        assert service.job.close.called is False
