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

from homehub_backend.app import app
from homehub_backend.lib import services
from homehub_backend.testing.services import FakeService, FakeGlobalService


class Test_BaseService:
    def tearDown(self):
        app['SUBSCRIBERS'] = []

    @mock.patch.object(app['STATE'], 'get')
    def test_init(self, mock_app_state_get):
        mock_app_state_get.return_value = {
            'backend': {
                'testing.FakeService.fake_instance': 'fake_state',
            },
        }
        instance = 'fake_instance'
        characteristics = {'spam': True}

        service = FakeService(app, instance, characteristics)
        assert service.app == app
        assert service.instance == instance
        assert service.characteristics == characteristics
        assert service.capabilities == {}
        assert service.state == 'fake_state'

    @mock.patch.object(app['STATE'], 'get')
    def test_service_not_global(self, mock_app_state_get):
        mock_app_state_get.return_value = {
            'backend': {
                'testing.FakeService.fake_instance': 'fake_state',
            },
        }
        instance = 'fake_instance'
        characteristics = {'spam': True}

        service = services.BaseService.service(
            FakeService, app, instance, characteristics,
        )
        assert FakeService.INSTANCE is None
        assert service.app == app
        assert service.instance == instance
        assert service.characteristics == characteristics
        assert service.state == 'fake_state'

    @mock.patch.object(app['STATE'], 'get')
    def test_service_global(self, mock_app_state_get):
        mock_app_state_get.return_value = {
            'backend': {
                'testing.FakeGlobalService.None': 'fake_state',
            },
        }
        instance = 'fake_instance'
        characteristics = {'spam': True}

        service = services.BaseService.service(
            FakeGlobalService, app, instance, characteristics,
        )
        assert FakeGlobalService.INSTANCE == service
        assert service.app == app
        assert service.instance is None
        assert service.characteristics == characteristics
        assert service.state == 'fake_state'

    @mock.patch.object(app['STATE'], 'get')
    def test_state_key(self, mock_app_state_get):
        mock_app_state_get.return_value = {
            'backend': {
                'testing.FakeService.fake_instance': 'fake_state',
            },
        }
        instance = 'fake_instance'
        characteristics = {'spam': True}

        service = FakeService(app, instance, characteristics)

        result = service.state_key()
        assert result == 'testing.FakeService.fake_instance'

    async def test_current_data(self):
        with mock.patch.object(app['STATE'], 'get') as mock_app_state_get:
            mock_app_state_get.return_value = {
                'backend': {
                    'testing.FakeService.fake_instance': 'fake_state',
                },
            }
            instance = 'fake_instance'
            characteristics = {'spam': True}

            service = FakeService(app, instance, characteristics)

            result = await service.current_data()
            assert isinstance(result, services.ServiceData)
            assert result.data == 'spam'

    async def test_shutdown(self):
        with mock.patch.object(app['STATE'], 'get') as mock_app_state_get:
            mock_app_state_get.return_value = {
                'backend': {
                    'testing.FakeService.fake_instance': 'fake_state',
                },
            }
            instance = 'fake_instance'
            characteristics = {'spam': True}

            service = FakeService(app, instance, characteristics)

            with mock.patch.object(service, 'stop'):
                await service.shutdown()
                assert service.stop.called is True

    async def test_notify(self):
        with mock.patch.object(app['STATE'], 'get') as mock_app_state_get:
            mock_app_state_get.return_value = {
                'backend': {
                    'testing.FakeService.fake_instance': 'fake_state',
                },
            }
            instance = 'fake_instance'
            characteristics = {'spam': True}

            service = FakeService(app, instance, characteristics)

            with mock.patch.object(app, 'notify_subscribers'):
                await service.notify()

                app.notify_subscribers.assert_called_with({
                    'type': 'SERVICE_NOTIFICATION',
                    'kind': FakeService.KIND,
                    'instance': 'fake_instance',
                    'data': service.make_data().to_json(),
                })

    async def test_notify_global(self):
        with mock.patch.object(app['STATE'], 'get') as mock_app_state_get:
            mock_app_state_get.return_value = {
                'backend': {
                    'testing.FakeService.fake_instance': 'fake_state',
                },
            }
            instance = 'fake_instance'
            characteristics = {'spam': True}

            service = FakeGlobalService(app, instance, characteristics)

            with mock.patch.object(app, 'notify_subscribers'):
                await service.notify()

                app.notify_subscribers.assert_called_with({
                    'type': 'SERVICE_NOTIFICATION',
                    'kind': FakeGlobalService.KIND,
                    'instance': None,
                    'data': service.data.to_json(),
                })

    async def test_notify_exception(self):
        with mock.patch.object(app['STATE'], 'get') as mock_app_state_get:
            mock_app_state_get.return_value = {
                'backend': {
                    'testing.FakeService.fake_instance': 'fake_state',
                },
            }
            instance = 'fake_instance'
            characteristics = {'spam': True}

            service = FakeService(app, instance, characteristics)

            with mock.patch.object(service, 'current_data') as mock_current_data:
                mock_current_data.side_effect = RuntimeError('TODO')

                with mock.patch.object(app, 'notify_subscribers'):
                    await service.notify()

                    app.notify_subscribers.assert_called_with({
                        'type': 'SERVICE_NOTIFICATION',
                        'kind': FakeService.KIND,
                        'instance': 'fake_instance',
                        'data': {
                            'error': 'TODO',
                        },
                    })

    @mock.patch.object(app['STATE'], 'get')
    @mock.patch.object(app['STATE'], 'save')
    def test_set_state(self, mock_app_state_save, mock_app_state_get):
        mock_app_state_get.return_value = {
            'backend': {
                'testing.FakeService.fake_instance': 'fake_state',
            },
        }
        instance = 'fake_instance'
        characteristics = {'spam': True}

        service = FakeService(app, instance, characteristics)

        service.set_state('spam')
        assert service.state == 'spam'
        mock_app_state_save.assert_called_with({
            'backend': {
                'testing.FakeService.fake_instance': 'spam',
            },
        })

    def test_register_capability(self):
        service = FakeService(app, 'fake_instance', {})

        service.register_capability('capability', service.capability)

        expected_capabilities = {
            'capability': services.CapabilitySpec(
                service.capability, False, None,
            ),
        }
        assert service.capabilities == expected_capabilities

    def test_register_capability_with_public(self):
        service = FakeService(app, 'fake_instance', {})

        service.register_capability(
            'capability',
            service.capability,
            public=True,
            public_content_type='text/plain',
        )

        assert len(service.capabilities) == 1

        expected_capabilities = {
            'capability': services.CapabilitySpec(
                service.capability, True, 'text/plain',
            ),
        }
        assert service.capabilities == expected_capabilities
