"""The tests for the APNS component."""
import io
import unittest
from unittest.mock import Mock, patch, mock_open

from apns2.errors import Unregistered
import yaml

import homeassistant.components.notify as notify
from homeassistant.setup import setup_component
from homeassistant.components.notify import apns
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'
    }
}


@patch('homeassistant.components.notify.apns.open', mock_open(), create=True)
class TestApns(unittest.TestCase):
    """Test the APNS component."""

    def setUp(self):  # pylint: disable=invalid-name
        """Set up 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):
        assert isinstance(apns.load_yaml_config_file, Mock), \
            'Found unmocked load_yaml'

        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]

    @patch('homeassistant.components.notify.apns._write_device')
    def test_register_new_device(self, mock_write):
        """Test registering a new device with a name."""
        yaml_file = {5678: {'name': 'test device 2'}}

        written_devices = []

        def fake_write(_out, device):
            """Fake write_device."""
            written_devices.append(device)

        mock_write.side_effect = fake_write

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        assert len(written_devices) == 1
        assert written_devices[0].name == 'test device'

    @patch('homeassistant.components.notify.apns._write_device')
    def test_register_device_without_name(self, mock_write):
        """Test registering a without a name."""
        yaml_file = {
            1234: {
                'name': 'test device 1',
                'tracking_device_id': 'tracking123',
            },
            5678: {
                'name': 'test device 2',
                'tracking_device_id': 'tracking456',
            },
        }

        written_devices = []

        def fake_write(_out, device):
            """Fake write_device."""
            written_devices.append(device)

        mock_write.side_effect = fake_write

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        devices = {dev.push_id: dev for dev in written_devices}

        test_device = devices.get('1234')

        assert test_device is not None
        assert test_device.name is None

    @patch('homeassistant.components.notify.apns._write_device')
    def test_update_existing_device(self, mock_write):
        """Test updating an existing device."""
        yaml_file = {
            1234: {
                'name': 'test device 1',
            },
            5678: {
                'name': 'test device 2',
            },
        }

        written_devices = []

        def fake_write(_out, device):
            """Fake write_device."""
            written_devices.append(device)

        mock_write.side_effect = fake_write

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        devices = {dev.push_id: dev for dev in written_devices}

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

        assert test_device_1 is not None
        assert test_device_2 is not None

        assert 'updated device 1' == test_device_1.name

    @patch('homeassistant.components.notify.apns._write_device')
    def test_update_existing_device_with_tracking_id(self, mock_write):
        """Test updating an existing device that has a tracking id."""
        yaml_file = {
            1234: {
                'name': 'test device 1',
                'tracking_device_id': 'tracking123',
            },
            5678: {
                'name': 'test device 2',
                'tracking_device_id': 'tracking456',
            },
        }

        written_devices = []

        def fake_write(_out, device):
            """Fake write_device."""
            written_devices.append(device)

        mock_write.side_effect = fake_write

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        devices = {dev.push_id: dev for dev in written_devices}

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

        assert test_device_1 is not None
        assert test_device_2 is not None

        assert 'tracking123' == \
            test_device_1.tracking_device_id
        assert 'tracking456' == \
            test_device_2.tracking_device_id

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

        yaml_file = {1234: {'name': 'test device 1'}}

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        assert send.called
        assert 1 == len(send.mock_calls)

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

        assert '1234' == target
        assert 'Hello' == payload.alert
        assert 1 == payload.badge
        assert 'test.mp3' == payload.sound
        assert '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

        yaml_file = {1234: {
            'name': 'test device 1',
            'disabled': True,
        }}

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        assert not 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

        yaml_file = {
            1234: {
                'name': 'test device 1',
                'tracking_device_id': 'tracking123',
            },
            5678: {
                'name': 'test device 2',
                'tracking_device_id': 'tracking456',
            },
        }

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)), \
                patch('os.path.isfile', Mock(return_value=True)):
            notify_service = apns.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'))

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

        assert send.called
        assert 1 == len(send.mock_calls)

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

        assert '5678' == target
        assert 'Hello' == payload.alert

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

        yaml_file = {
            1234: {
                'name': 'test device 1',
                'tracking_device_id': 'tracking123',
            },
            5678: {
                'name': 'test device 2',
                'tracking_device_id': 'tracking456',
            },
        }

        written_devices = []

        def fake_write(_out, device):
            """Fake write_device."""
            written_devices.append(device)

        mock_write.side_effect = fake_write

        with patch(
            'homeassistant.components.notify.apns.load_yaml_config_file',
                Mock(return_value=yaml_file)):
            self._setup_notify()

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

        devices = {dev.push_id: dev for dev in written_devices}

        test_device_1 = devices.get('1234')
        assert test_device_1 is not None
        assert test_device_1.disabled is True


def test_write_device():
    """Test writing device."""
    out = io.StringIO()
    device = apns.ApnsDevice('123', 'name', 'track_id', True)

    apns._write_device(out, device)
    data = yaml.load(out.getvalue())
    assert data == {
        123: {
            'name': 'name',
            'tracking_device_id': 'track_id',
            'disabled': True
        },
    }