"""The tests for the APNS component."""
import os
import unittest
from unittest.mock import patch
from unittest.mock import Mock

from apns2.errors import Unregistered

import homeassistant.components.notify as notify
from homeassistant.bootstrap import setup_component
from homeassistant.components.notify.apns import ApnsNotificationService
from homeassistant.config import load_yaml_config_file
from homeassistant.core import State
from tests.common import assert_setup_component, get_test_home_assistant

CONFIG = {
    notify.DOMAIN: {
        'platform': 'apns',
        'name': 'test_app',
        'topic': 'testapp.appname',
        'cert_file': 'test_app.pem'
    }
}


class TestApns(unittest.TestCase):
    """Test the APNS component."""

    def setUp(self):  # pylint: disable=invalid-name
        """Setup things to be run when tests are started."""
        self.hass = get_test_home_assistant()

    def tearDown(self):  # pylint: disable=invalid-name
        """Stop everything that was started."""
        self.hass.stop()

    @patch('os.path.isfile', Mock(return_value=True))
    @patch('os.access', Mock(return_value=True))
    def _setup_notify(self):
        with assert_setup_component(1) as handle_config:
            assert setup_component(self.hass, notify.DOMAIN, CONFIG)
        assert handle_config[notify.DOMAIN]

    @patch('os.path.isfile', return_value=True)
    @patch('os.access', return_value=True)
    def test_apns_setup_full(self, mock_access, mock_isfile):
        """Test setup with all data."""
        config = {
            'notify': {
                'platform': 'apns',
                'name': 'test_app',
                'sandbox': 'True',
                'topic': 'testapp.appname',
                'cert_file': 'test_app.pem'
            }
        }

        with assert_setup_component(1) as handle_config:
            assert setup_component(self.hass, notify.DOMAIN, config)
        assert handle_config[notify.DOMAIN]

    def test_apns_setup_missing_name(self):
        """Test setup with missing name."""
        config = {
            'notify': {
                'platform': 'apns',
                'topic': 'testapp.appname',
                'cert_file': 'test_app.pem',
            }
        }
        with assert_setup_component(0) as handle_config:
            assert setup_component(self.hass, notify.DOMAIN, config)
        assert not handle_config[notify.DOMAIN]

    def test_apns_setup_missing_certificate(self):
        """Test setup with missing certificate."""
        config = {
            'notify': {
                'platform': 'apns',
                'name': 'test_app',
                'topic': 'testapp.appname',
            }
        }
        with assert_setup_component(0) as handle_config:
            assert setup_component(self.hass, notify.DOMAIN, config)
        assert not handle_config[notify.DOMAIN]

    def test_apns_setup_missing_topic(self):
        """Test setup with missing topic."""
        config = {
            'notify': {
                'platform': 'apns',
                'name': 'test_app',
                'cert_file': 'test_app.pem',
            }
        }
        with assert_setup_component(0) as handle_config:
            assert setup_component(self.hass, notify.DOMAIN, config)
        assert not handle_config[notify.DOMAIN]

    def test_register_new_device(self):
        """Test registering a new device with a name."""
        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('5678: {name: test device 2}\n')

        self._setup_notify()
        self.assertTrue(self.hass.services.call(notify.DOMAIN,
                                                'apns_test_app',
                                                {'push_id': '1234',
                                                 'name': 'test device'},
                                                blocking=True))

        devices = {str(key): value for (key, value) in
                   load_yaml_config_file(devices_path).items()}

        test_device_1 = devices.get('1234')
        test_device_2 = devices.get('5678')

        self.assertIsNotNone(test_device_1)
        self.assertIsNotNone(test_device_2)

        self.assertEqual('test device', test_device_1.get('name'))

        os.remove(devices_path)

    def test_register_device_without_name(self):
        """Test registering a without a name."""
        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('5678: {name: test device 2}\n')

        self._setup_notify()
        self.assertTrue(self.hass.services.call(notify.DOMAIN, 'apns_test_app',
                                                {'push_id': '1234'},
                                                blocking=True))

        devices = {str(key): value for (key, value) in
                   load_yaml_config_file(devices_path).items()}

        test_device = devices.get('1234')

        self.assertIsNotNone(test_device)
        self.assertIsNone(test_device.get('name'))

        os.remove(devices_path)

    def test_update_existing_device(self):
        """Test updating an existing device."""
        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1}\n')
            out.write('5678: {name: test device 2}\n')

        self._setup_notify()
        self.assertTrue(self.hass.services.call(notify.DOMAIN,
                                                'apns_test_app',
                                                {'push_id': '1234',
                                                 'name': 'updated device 1'},
                                                blocking=True))

        devices = {str(key): value for (key, value) in
                   load_yaml_config_file(devices_path).items()}

        test_device_1 = devices.get('1234')
        test_device_2 = devices.get('5678')

        self.assertIsNotNone(test_device_1)
        self.assertIsNotNone(test_device_2)

        self.assertEqual('updated device 1', test_device_1.get('name'))

        os.remove(devices_path)

    def test_update_existing_device_with_tracking_id(self):
        """Test updating an existing device that has a tracking id."""
        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1, '
                      'tracking_device_id: tracking123}\n')
            out.write('5678: {name: test device 2, '
                      'tracking_device_id: tracking456}\n')

        self._setup_notify()
        self.assertTrue(self.hass.services.call(notify.DOMAIN,
                                                'apns_test_app',
                                                {'push_id': '1234',
                                                 'name': 'updated device 1'},
                                                blocking=True))

        devices = {str(key): value for (key, value) in
                   load_yaml_config_file(devices_path).items()}

        test_device_1 = devices.get('1234')
        test_device_2 = devices.get('5678')

        self.assertIsNotNone(test_device_1)
        self.assertIsNotNone(test_device_2)

        self.assertEqual('tracking123',
                         test_device_1.get('tracking_device_id'))
        self.assertEqual('tracking456',
                         test_device_2.get('tracking_device_id'))

        os.remove(devices_path)

    @patch('apns2.client.APNsClient')
    def test_send(self, mock_client):
        """Test updating an existing device."""
        send = mock_client.return_value.send_notification

        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1}\n')

        self._setup_notify()

        self.assertTrue(self.hass.services.call(
            'notify', 'test_app',
            {'message': 'Hello', 'data': {
                'badge': 1,
                'sound': 'test.mp3',
                'category': 'testing'}},
            blocking=True))

        self.assertTrue(send.called)
        self.assertEqual(1, len(send.mock_calls))

        target = send.mock_calls[0][1][0]
        payload = send.mock_calls[0][1][1]

        self.assertEqual('1234', target)
        self.assertEqual('Hello', payload.alert)
        self.assertEqual(1, payload.badge)
        self.assertEqual('test.mp3', payload.sound)
        self.assertEqual('testing', payload.category)

    @patch('apns2.client.APNsClient')
    def test_send_when_disabled(self, mock_client):
        """Test updating an existing device."""
        send = mock_client.return_value.send_notification

        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1, disabled: True}\n')

        self._setup_notify()

        self.assertTrue(self.hass.services.call(
            'notify', 'test_app',
            {'message': 'Hello', 'data': {
                'badge': 1,
                'sound': 'test.mp3',
                'category': 'testing'}},
            blocking=True))

        self.assertFalse(send.called)

    @patch('apns2.client.APNsClient')
    def test_send_with_state(self, mock_client):
        """Test updating an existing device."""
        send = mock_client.return_value.send_notification

        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1, '
                      'tracking_device_id: tracking123}\n')
            out.write('5678: {name: test device 2, '
                      'tracking_device_id: tracking456}\n')

        notify_service = ApnsNotificationService(
            self.hass,
            'test_app',
            'testapp.appname',
            False,
            'test_app.pem'
        )

        notify_service.device_state_changed_listener(
            'device_tracker.tracking456',
            State('device_tracker.tracking456', None),
            State('device_tracker.tracking456', 'home'))

        self.hass.block_till_done()

        notify_service.send_message(message='Hello', target='home')

        self.assertTrue(send.called)
        self.assertEqual(1, len(send.mock_calls))

        target = send.mock_calls[0][1][0]
        payload = send.mock_calls[0][1][1]

        self.assertEqual('5678', target)
        self.assertEqual('Hello', payload.alert)

    @patch('apns2.client.APNsClient')
    def test_disable_when_unregistered(self, mock_client):
        """Test disabling a device when it is unregistered."""
        send = mock_client.return_value.send_notification
        send.side_effect = Unregistered()

        devices_path = self.hass.config.path('test_app_apns.yaml')
        with open(devices_path, 'w+') as out:
            out.write('1234: {name: test device 1}\n')

        self._setup_notify()

        self.assertTrue(self.hass.services.call('notify', 'test_app',
                                                {'message': 'Hello'},
                                                blocking=True))

        devices = {str(key): value for (key, value) in
                   load_yaml_config_file(devices_path).items()}

        test_device_1 = devices.get('1234')
        self.assertIsNotNone(test_device_1)
        self.assertEqual(True, test_device_1.get('disabled'))

        os.remove(devices_path)
