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

from homehub_backend.lib.services import ServiceData
import gmqtt
import pytest

from homehub_iformicarium import services


@pytest.fixture
def mock_mqtt_client():
    return mock.Mock(spec=gmqtt.Client)


class Test_iFormicariumService:
    def _valid_characteristics(self):
        return {
            'mqtt': 'thisisntright.local',
            'name': 'iformicarium',
            'topic': 'iformicarium',
        }

    def _valid_payload(self, **update):
        payload = {
            'name': 'iformicarium',
            'uptime': 123,
            'heap': 65535,
            'dht': {
                '0': {
                    'name': 'dht1',
                    'humi': 69,
                    'temp': 25,
                },
            },
            'fan': 1,
        }
        payload.update(update)

        return json.dumps(payload)

    def test_init(self, homehub_app):
        # Given
        service = services.iFormicariumService(
            homehub_app, 'testing', self._valid_characteristics(),
        )

        # Then
        assert service._current_data is None
        assert service.client is None

    async def test_current_data(self, homehub_app):
        # Given
        service = services.iFormicariumService(
            homehub_app, 'testing', self._valid_characteristics(),
        )
        service._current_data = ServiceData(data='spam')

        # When
        result = await service.current_data()

        # Then
        assert result is service._current_data

    async def test_current_data_empty(self, homehub_app):
        # Given
        service = services.iFormicariumService(
            homehub_app, 'testing', self._valid_characteristics(),
        )

        # When
        result = await service.current_data()

        # Then
        assert result != service._current_data
        assert result.data is None
        assert result.exception is None

    def test_on_client_connect(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        # When
        service.on_client_connect(
            mock_mqtt_client, 'flags', 'rc', 'properties',
        )

        # Then
        mock_mqtt_client.subscribe.assert_called_with(
            characteristics['topic'],
        )

    @pytest.mark.skip()
    def test_on_client_disconnect(self, homehub_app):
        pass

    async def test_on_client_message_json_decode_error(self, homehub_app,
                                                       mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        with mock.patch.object(service, 'notify') as mock_notify:
            # When
            await service.on_client_message(
                mock_mqtt_client,
                characteristics['topic'],
                '',
                None,
                None,
            )

            # Then
            assert mock_notify.called
            assert service._current_data.data is None
            assert isinstance(service._current_data.exception, BaseException)

    async def test_on_client_message_name_mismatch(self, homehub_app,
                                                   mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        fake_payload = self._valid_payload(name='otherinstance')

        with mock.patch.object(service, 'notify') as mock_notify:
            # When
            await service.on_client_message(
                mock_mqtt_client,
                characteristics['topic'],
                fake_payload,
                None,
                None,
            )

            # Then
            assert not mock_notify.called
            assert service._current_data is None

    @mock.patch('datetime.datetime')
    async def test_on_client_message(self, mock_datetime, homehub_app,
                                     mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        fake_payload = self._valid_payload()

        fake_now = datetime.datetime(1987, 10, 3, 8, 0, 0)
        mock_datetime.now.return_value = fake_now

        with mock.patch.object(service, 'notify') as mock_notify:
            # When
            await service.on_client_message(
                mock_mqtt_client,
                characteristics['topic'],
                fake_payload,
                None,
                None,
            )

            # Then
            assert mock_notify.called
            assert service._current_data is not None
            assert service._current_data.exception is None

            data = json.loads(fake_payload)
            assert service._current_data.data['name'] == data['name']
            assert service._current_data.data['uptime'] == data['uptime']
            assert service._current_data.data['heap'] == data['heap']
            assert service._current_data.data['dht'] == data['dht']
            assert service._current_data.data['dt'] == fake_now.timestamp()

    async def test_start_already_started(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )
        service.client = mock_mqtt_client

        fake_other_client = mock.Mock(spec=gmqtt.Client)

        with mock.patch.object(gmqtt, 'Client') as mock_client:
            mock_client.return_value = fake_other_client

            # When
            await service.start()

            # Then
            assert service.client == mock_mqtt_client
            assert not mock_client.called

    async def test_start(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        with mock.patch.object(gmqtt, 'Client') as mock_client:
            mock_client.return_value = mock_mqtt_client

            # When
            await service.start()

            # Then
            assert service.client == mock_mqtt_client
            assert service.client.on_connect == service.on_client_connect
            assert service.client.on_disconnect == service.on_client_disconnect
            assert service.client.on_message == service.on_client_message

            assert mock_client.called
            mock_mqtt_client.set_config.assert_called_with({
                'reconnect_retries': 10,
            })
            mock_mqtt_client.connect.assert_called_with(
                characteristics['mqtt'],
            )

    async def test_stop_not_started(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )

        # When
        await service.stop()

        # Then
        assert not mock_mqtt_client.disconnect.called

    async def test_stop_not_connected(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )
        service.client = mock_mqtt_client
        service.client.is_connected = False

        # When
        await service.stop()

        # Then
        assert not mock_mqtt_client.disconnect.called

    async def test_stop(self, homehub_app, mock_mqtt_client):
        # Given
        characteristics = self._valid_characteristics()
        service = services.iFormicariumService(
            homehub_app, 'testing', characteristics,
        )
        service.client = mock_mqtt_client
        service.client.is_connected = True

        # When
        await service.stop()

        # Then
        assert mock_mqtt_client.disconnect.called
