"""Tests for the smartapp module."""
from unittest.mock import Mock, patch
from uuid import uuid4

from pysmartthings import AppEntity, Capability

from homeassistant.components.smartthings import smartapp
from homeassistant.components.smartthings.const import (
    CONF_INSTALLED_APP_ID, CONF_INSTALLED_APPS, CONF_LOCATION_ID,
    CONF_REFRESH_TOKEN, DATA_MANAGER, DOMAIN)

from tests.common import mock_coro


async def test_update_app(hass, app):
    """Test update_app does not save if app is current."""
    await smartapp.update_app(hass, app)
    assert app.save.call_count == 0


async def test_update_app_updated_needed(hass, app):
    """Test update_app updates when an app is needed."""
    mock_app = Mock(spec=AppEntity)
    mock_app.app_name = 'Test'
    mock_app.refresh.return_value = mock_coro()
    mock_app.save.return_value = mock_coro()

    await smartapp.update_app(hass, mock_app)

    assert mock_app.save.call_count == 1
    assert mock_app.app_name == 'Test'
    assert mock_app.display_name == app.display_name
    assert mock_app.description == app.description
    assert mock_app.webhook_target_url == app.webhook_target_url
    assert mock_app.app_type == app.app_type
    assert mock_app.single_instance == app.single_instance
    assert mock_app.classifications == app.classifications


async def test_smartapp_install_store_if_no_other(
        hass, smartthings_mock, device_factory):
    """Test aborts if no other app was configured already."""
    # Arrange
    app = Mock()
    app.app_id = uuid4()
    request = Mock()
    request.installed_app_id = str(uuid4())
    request.auth_token = str(uuid4())
    request.location_id = str(uuid4())
    request.refresh_token = str(uuid4())
    # Act
    await smartapp.smartapp_install(hass, request, None, app)
    # Assert
    entries = hass.config_entries.async_entries('smartthings')
    assert not entries
    data = hass.data[DOMAIN][CONF_INSTALLED_APPS][0]
    assert data[CONF_REFRESH_TOKEN] == request.refresh_token
    assert data[CONF_LOCATION_ID] == request.location_id
    assert data[CONF_INSTALLED_APP_ID] == request.installed_app_id


async def test_smartapp_install_creates_flow(
        hass, smartthings_mock, config_entry, location, device_factory):
    """Test installation creates flow."""
    # Arrange
    setattr(hass.config_entries, '_entries', [config_entry])
    api = smartthings_mock.return_value
    app = Mock()
    app.app_id = config_entry.data['app_id']
    request = Mock()
    request.installed_app_id = str(uuid4())
    request.auth_token = str(uuid4())
    request.refresh_token = str(uuid4())
    request.location_id = location.location_id
    devices = [
        device_factory('', [Capability.battery, 'ping']),
        device_factory('', [Capability.switch, Capability.switch_level]),
        device_factory('', [Capability.switch])
    ]
    api.devices = Mock()
    api.devices.return_value = mock_coro(return_value=devices)
    # Act
    await smartapp.smartapp_install(hass, request, None, app)
    # Assert
    await hass.async_block_till_done()
    entries = hass.config_entries.async_entries('smartthings')
    assert len(entries) == 2
    assert entries[1].data['app_id'] == app.app_id
    assert entries[1].data['installed_app_id'] == request.installed_app_id
    assert entries[1].data['location_id'] == request.location_id
    assert entries[1].data['access_token'] == \
        config_entry.data['access_token']
    assert entries[1].data['refresh_token'] == request.refresh_token
    assert entries[1].data['client_secret'] == \
        config_entry.data['client_secret']
    assert entries[1].data['client_id'] == config_entry.data['client_id']
    assert entries[1].title == location.name


async def test_smartapp_update_saves_token(
        hass, smartthings_mock, location, device_factory):
    """Test update saves token."""
    # Arrange
    entry = Mock()
    entry.data = {
        'installed_app_id': str(uuid4()),
        'app_id': str(uuid4())
    }
    entry.domain = DOMAIN

    setattr(hass.config_entries, '_entries', [entry])
    app = Mock()
    app.app_id = entry.data['app_id']
    request = Mock()
    request.installed_app_id = entry.data['installed_app_id']
    request.auth_token = str(uuid4())
    request.refresh_token = str(uuid4())
    request.location_id = location.location_id

    # Act
    await smartapp.smartapp_update(hass, request, None, app)
    # Assert
    assert entry.data[CONF_REFRESH_TOKEN] == request.refresh_token


async def test_smartapp_uninstall(hass, config_entry):
    """Test the config entry is unloaded when the app is uninstalled."""
    setattr(hass.config_entries, '_entries', [config_entry])
    app = Mock()
    app.app_id = config_entry.data['app_id']
    request = Mock()
    request.installed_app_id = config_entry.data['installed_app_id']

    with patch.object(hass.config_entries, 'async_remove',
                      return_value=mock_coro()) as remove:
        await smartapp.smartapp_uninstall(hass, request, None, app)
        assert remove.call_count == 1


async def test_smartapp_webhook(hass):
    """Test the smartapp webhook calls the manager."""
    manager = Mock()
    manager.handle_request = Mock()
    manager.handle_request.return_value = mock_coro(return_value={})
    hass.data[DOMAIN][DATA_MANAGER] = manager
    request = Mock()
    request.headers = []
    request.json.return_value = mock_coro(return_value={})
    result = await smartapp.smartapp_webhook(hass, '', request)

    assert result.body == b'{}'


async def test_smartapp_sync_subscriptions(
        hass, smartthings_mock, device_factory, subscription_factory):
    """Test synchronization adds and removes."""
    api = smartthings_mock.return_value
    api.delete_subscription.side_effect = lambda loc_id, sub_id: mock_coro()
    api.create_subscription.side_effect = lambda sub: mock_coro()
    subscriptions = [
        subscription_factory(Capability.thermostat),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level)
    ]
    api.subscriptions.return_value = mock_coro(return_value=subscriptions)
    devices = [
        device_factory('', [Capability.battery, 'ping']),
        device_factory('', [Capability.switch, Capability.switch_level]),
        device_factory('', [Capability.switch])
    ]

    await smartapp.smartapp_sync_subscriptions(
        hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)

    assert api.subscriptions.call_count == 1
    assert api.delete_subscription.call_count == 1
    assert api.create_subscription.call_count == 1


async def test_smartapp_sync_subscriptions_up_to_date(
        hass, smartthings_mock, device_factory, subscription_factory):
    """Test synchronization does nothing when current."""
    api = smartthings_mock.return_value
    api.delete_subscription.side_effect = lambda loc_id, sub_id: mock_coro()
    api.create_subscription.side_effect = lambda sub: mock_coro()
    subscriptions = [
        subscription_factory(Capability.battery),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level)
    ]
    api.subscriptions.return_value = mock_coro(return_value=subscriptions)
    devices = [
        device_factory('', [Capability.battery, 'ping']),
        device_factory('', [Capability.switch, Capability.switch_level]),
        device_factory('', [Capability.switch])
    ]

    await smartapp.smartapp_sync_subscriptions(
        hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)

    assert api.subscriptions.call_count == 1
    assert api.delete_subscription.call_count == 0
    assert api.create_subscription.call_count == 0


async def test_smartapp_sync_subscriptions_handles_exceptions(
        hass, smartthings_mock, device_factory, subscription_factory):
    """Test synchronization does nothing when current."""
    api = smartthings_mock.return_value
    api.delete_subscription.side_effect = \
        lambda loc_id, sub_id: mock_coro(exception=Exception)
    api.create_subscription.side_effect = \
        lambda sub: mock_coro(exception=Exception)
    subscriptions = [
        subscription_factory(Capability.battery),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level)
    ]
    api.subscriptions.return_value = mock_coro(return_value=subscriptions)
    devices = [
        device_factory('', [Capability.thermostat, 'ping']),
        device_factory('', [Capability.switch, Capability.switch_level]),
        device_factory('', [Capability.switch])
    ]

    await smartapp.smartapp_sync_subscriptions(
        hass, str(uuid4()), str(uuid4()), str(uuid4()), devices)

    assert api.subscriptions.call_count == 1
    assert api.delete_subscription.call_count == 1
    assert api.create_subscription.call_count == 1