"""Test HTML5 notify platform."""
import json
from unittest.mock import patch, MagicMock, mock_open
from aiohttp.hdrs import AUTHORIZATION

from homeassistant.setup import async_setup_component
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.notify import html5

CONFIG_FILE = 'file.conf'

SUBSCRIPTION_1 = {
    'browser': 'chrome',
    'subscription': {
        'endpoint': 'https://googleapis.com',
        'keys': {'auth': 'auth', 'p256dh': 'p256dh'}
    },
}
SUBSCRIPTION_2 = {
    'browser': 'firefox',
    'subscription': {
        'endpoint': 'https://example.com',
        'keys': {
            'auth': 'bla',
            'p256dh': 'bla',
        },
    },
}
SUBSCRIPTION_3 = {
    'browser': 'chrome',
    'subscription': {
        'endpoint': 'https://example.com/not_exist',
        'keys': {
            'auth': 'bla',
            'p256dh': 'bla',
        },
    },
}
SUBSCRIPTION_4 = {
    'browser': 'chrome',
    'subscription': {
        'endpoint': 'https://googleapis.com',
        'expirationTime': None,
        'keys': {'auth': 'auth', 'p256dh': 'p256dh'}
    },
}

REGISTER_URL = '/api/notify.html5'
PUBLISH_URL = '/api/notify.html5/callback'


async def mock_client(hass, hass_client, registrations=None):
    """Create a test client for HTML5 views."""
    if registrations is None:
        registrations = {}

    with patch('homeassistant.components.notify.html5._load_config',
               return_value=registrations):
        await async_setup_component(hass, 'notify', {
            'notify': {
                'platform': 'html5'
            }
        })

    return await hass_client()


class TestHtml5Notify:
    """Tests for HTML5 notify platform."""

    def test_get_service_with_no_json(self):
        """Test empty json file."""
        hass = MagicMock()

        m = mock_open()
        with patch(
            'homeassistant.util.json.open',
            m, create=True
        ):
            service = html5.get_service(hass, {})

        assert service is not None

    @patch('pywebpush.WebPusher')
    def test_dismissing_message(self, mock_wp):
        """Test dismissing message."""
        hass = MagicMock()

        data = {
            'device': SUBSCRIPTION_1
        }

        m = mock_open(read_data=json.dumps(data))
        with patch(
            'homeassistant.util.json.open',
            m, create=True
        ):
            service = html5.get_service(hass, {'gcm_sender_id': '100'})

        assert service is not None

        service.dismiss(target=['device', 'non_existing'],
                        data={'tag': 'test'})

        assert len(mock_wp.mock_calls) == 3

        # WebPusher constructor
        assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1['subscription']
        # Third mock_call checks the status_code of the response.
        assert mock_wp.mock_calls[2][0] == '().send().status_code.__eq__'

        # Call to send
        payload = json.loads(mock_wp.mock_calls[1][1][0])

        assert payload['dismiss'] is True
        assert payload['tag'] == 'test'

    @patch('pywebpush.WebPusher')
    def test_sending_message(self, mock_wp):
        """Test sending message."""
        hass = MagicMock()

        data = {
            'device': SUBSCRIPTION_1
        }

        m = mock_open(read_data=json.dumps(data))
        with patch(
            'homeassistant.util.json.open',
            m, create=True
        ):
            service = html5.get_service(hass, {'gcm_sender_id': '100'})

        assert service is not None

        service.send_message('Hello', target=['device', 'non_existing'],
                             data={'icon': 'beer.png'})

        assert len(mock_wp.mock_calls) == 3

        # WebPusher constructor
        assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1['subscription']
        # Third mock_call checks the status_code of the response.
        assert mock_wp.mock_calls[2][0] == '().send().status_code.__eq__'

        # Call to send
        payload = json.loads(mock_wp.mock_calls[1][1][0])

        assert payload['body'] == 'Hello'
        assert payload['icon'] == 'beer.png'

    @patch('pywebpush.WebPusher')
    def test_gcm_key_include(self, mock_wp):
        """Test if the gcm_key is only included for GCM endpoints."""
        hass = MagicMock()

        data = {
            'chrome': SUBSCRIPTION_1,
            'firefox': SUBSCRIPTION_2
        }

        m = mock_open(read_data=json.dumps(data))
        with patch('homeassistant.util.json.open', m, create=True):
            service = html5.get_service(hass, {
                'gcm_sender_id': '100',
                'gcm_api_key': 'Y6i0JdZ0mj9LOaSI'
            })

        assert service is not None

        service.send_message('Hello', target=['chrome', 'firefox'])

        assert len(mock_wp.mock_calls) == 6

        # WebPusher constructor
        assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1['subscription']
        assert mock_wp.mock_calls[3][1][0] == SUBSCRIPTION_2['subscription']

        # Third mock_call checks the status_code of the response.
        assert mock_wp.mock_calls[2][0] == '().send().status_code.__eq__'
        assert mock_wp.mock_calls[5][0] == '().send().status_code.__eq__'

        # Get the keys passed to the WebPusher's send method
        assert mock_wp.mock_calls[1][2]['gcm_key'] is not None
        assert mock_wp.mock_calls[4][2]['gcm_key'] is None


async def test_registering_new_device_view(hass, hass_client):
    """Test that the HTML view works."""
    client = await mock_client(hass, hass_client)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1))

    assert resp.status == 200
    assert len(mock_save.mock_calls) == 1
    assert mock_save.mock_calls[0][1][1] == {
        'unnamed device': SUBSCRIPTION_1,
    }


async def test_registering_new_device_view_with_name(hass, hass_client):
    """Test that the HTML view works with name attribute."""
    client = await mock_client(hass, hass_client)

    SUB_WITH_NAME = SUBSCRIPTION_1.copy()
    SUB_WITH_NAME['name'] = 'test device'

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        resp = await client.post(REGISTER_URL, data=json.dumps(SUB_WITH_NAME))

    assert resp.status == 200
    assert len(mock_save.mock_calls) == 1
    assert mock_save.mock_calls[0][1][1] == {
        'test device': SUBSCRIPTION_1,
    }


async def test_registering_new_device_expiration_view(hass, hass_client):
    """Test that the HTML view works."""
    client = await mock_client(hass, hass_client)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4))

    assert resp.status == 200
    assert mock_save.mock_calls[0][1][1] == {
        'unnamed device': SUBSCRIPTION_4,
    }


async def test_registering_new_device_fails_view(hass, hass_client):
    """Test subs. are not altered when registering a new device fails."""
    registrations = {}
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json',
               side_effect=HomeAssistantError()):
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4))

    assert resp.status == 500
    assert registrations == {}


async def test_registering_existing_device_view(hass, hass_client):
    """Test subscription is updated when registering existing device."""
    registrations = {}
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1))
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4))

    assert resp.status == 200
    assert mock_save.mock_calls[0][1][1] == {
        'unnamed device': SUBSCRIPTION_4,
    }
    assert registrations == {
        'unnamed device': SUBSCRIPTION_4,
    }


async def test_registering_existing_device_view_with_name(hass, hass_client):
    """Test subscription is updated when reg'ing existing device with name."""
    registrations = {}
    client = await mock_client(hass, hass_client, registrations)

    SUB_WITH_NAME = SUBSCRIPTION_1.copy()
    SUB_WITH_NAME['name'] = 'test device'

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        await client.post(REGISTER_URL, data=json.dumps(SUB_WITH_NAME))
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4))

    assert resp.status == 200
    assert mock_save.mock_calls[0][1][1] == {
        'test device': SUBSCRIPTION_4,
    }
    assert registrations == {
        'test device': SUBSCRIPTION_4,
    }


async def test_registering_existing_device_fails_view(hass, hass_client):
    """Test sub. is not updated when registering existing device fails."""
    registrations = {}
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_1))
        mock_save.side_effect = HomeAssistantError
        resp = await client.post(REGISTER_URL, data=json.dumps(SUBSCRIPTION_4))

    assert resp.status == 500
    assert registrations == {
        'unnamed device': SUBSCRIPTION_1,
    }


async def test_registering_new_device_validation(hass, hass_client):
    """Test various errors when registering a new device."""
    client = await mock_client(hass, hass_client)

    resp = await client.post(REGISTER_URL, data=json.dumps({
        'browser': 'invalid browser',
        'subscription': 'sub info',
    }))
    assert resp.status == 400

    resp = await client.post(REGISTER_URL, data=json.dumps({
        'browser': 'chrome',
    }))
    assert resp.status == 400

    with patch('homeassistant.components.notify.html5.save_json',
               return_value=False):
        resp = await client.post(REGISTER_URL, data=json.dumps({
            'browser': 'chrome',
            'subscription': 'sub info',
        }))
    assert resp.status == 400


async def test_unregistering_device_view(hass, hass_client):
    """Test that the HTML unregister view works."""
    registrations = {
        'some device': SUBSCRIPTION_1,
        'other device': SUBSCRIPTION_2,
    }
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        resp = await client.delete(REGISTER_URL, data=json.dumps({
            'subscription': SUBSCRIPTION_1['subscription'],
        }))

    assert resp.status == 200
    assert len(mock_save.mock_calls) == 1
    assert registrations == {
        'other device': SUBSCRIPTION_2
    }


async def test_unregister_device_view_handle_unknown_subscription(
        hass, hass_client):
    """Test that the HTML unregister view handles unknown subscriptions."""
    registrations = {}
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json') as mock_save:
        resp = await client.delete(REGISTER_URL, data=json.dumps({
            'subscription': SUBSCRIPTION_3['subscription']
        }))

    assert resp.status == 200, resp.response
    assert registrations == {}
    assert len(mock_save.mock_calls) == 0


async def test_unregistering_device_view_handles_save_error(
        hass, hass_client):
    """Test that the HTML unregister view handles save errors."""
    registrations = {
        'some device': SUBSCRIPTION_1,
        'other device': SUBSCRIPTION_2,
    }
    client = await mock_client(hass, hass_client, registrations)

    with patch('homeassistant.components.notify.html5.save_json',
               side_effect=HomeAssistantError()):
        resp = await client.delete(REGISTER_URL, data=json.dumps({
            'subscription': SUBSCRIPTION_1['subscription'],
        }))

    assert resp.status == 500, resp.response
    assert registrations == {
        'some device': SUBSCRIPTION_1,
        'other device': SUBSCRIPTION_2,
    }


async def test_callback_view_no_jwt(hass, hass_client):
    """Test that the notification callback view works without JWT."""
    client = await mock_client(hass, hass_client)
    resp = await client.post(PUBLISH_URL, data=json.dumps({
        'type': 'push',
        'tag': '3bc28d69-0921-41f1-ac6a-7a627ba0aa72'
    }))

    assert resp.status == 401


async def test_callback_view_with_jwt(hass, hass_client):
    """Test that the notification callback view works with JWT."""
    registrations = {
        'device': SUBSCRIPTION_1
    }
    client = await mock_client(hass, hass_client, registrations)

    with patch('pywebpush.WebPusher') as mock_wp:
        await hass.services.async_call('notify', 'notify', {
            'message': 'Hello',
            'target': ['device'],
            'data': {'icon': 'beer.png'}
        }, blocking=True)

    assert len(mock_wp.mock_calls) == 3

    # WebPusher constructor
    assert mock_wp.mock_calls[0][1][0] == \
        SUBSCRIPTION_1['subscription']
    # Third mock_call checks the status_code of the response.
    assert mock_wp.mock_calls[2][0] == '().send().status_code.__eq__'

    # Call to send
    push_payload = json.loads(mock_wp.mock_calls[1][1][0])

    assert push_payload['body'] == 'Hello'
    assert push_payload['icon'] == 'beer.png'

    bearer_token = "Bearer {}".format(push_payload['data']['jwt'])

    resp = await client.post(PUBLISH_URL, json={
        'type': 'push',
    }, headers={AUTHORIZATION: bearer_token})

    assert resp.status == 200
    body = await resp.json()
    assert body == {"event": "push", "status": "ok"}