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

from pysmartthings import CAPABILITIES, AppEntity, Capability

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

from tests.common import MockConfigEntry


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(AppEntity)
    mock_app.app_name = "Test"

    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_update_saves_token(
    hass, smartthings_mock, location, device_factory
):
    """Test update saves token."""
    # Arrange
    entry = MockConfigEntry(
        domain=DOMAIN, data={"installed_app_id": str(uuid4()), "app_id": str(uuid4())}
    )
    entry.add_to_hass(hass)
    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."""
    config_entry.add_to_hass(hass)
    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") 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 = AsyncMock(return_value={})
    hass.data[DOMAIN][DATA_MANAGER] = manager
    request = Mock()
    request.headers = []
    request.json = AsyncMock(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 and ignores unused."""
    smartthings_mock.subscriptions.return_value = [
        subscription_factory(Capability.thermostat),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level),
    ]
    devices = [
        device_factory("", [Capability.battery, "ping"]),
        device_factory("", [Capability.switch, Capability.switch_level]),
        device_factory("", [Capability.switch, Capability.execute]),
    ]

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

    assert smartthings_mock.subscriptions.call_count == 1
    assert smartthings_mock.delete_subscription.call_count == 1
    assert smartthings_mock.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."""
    smartthings_mock.subscriptions.return_value = [
        subscription_factory(Capability.battery),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level),
    ]
    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 smartthings_mock.subscriptions.call_count == 1
    assert smartthings_mock.delete_subscription.call_count == 0
    assert smartthings_mock.create_subscription.call_count == 0


async def test_smartapp_sync_subscriptions_limit_warning(
    hass, smartthings_mock, device_factory, subscription_factory, caplog
):
    """Test synchronization over the limit logs a warning."""
    smartthings_mock.subscriptions.return_value = []
    devices = [
        device_factory("", CAPABILITIES),
    ]

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

    assert (
        "Some device attributes may not receive push updates and there may be "
        "subscription creation failures" in caplog.text
    )


async def test_smartapp_sync_subscriptions_handles_exceptions(
    hass, smartthings_mock, device_factory, subscription_factory
):
    """Test synchronization does nothing when current."""
    smartthings_mock.delete_subscription.side_effect = Exception
    smartthings_mock.create_subscription.side_effect = Exception
    smartthings_mock.subscriptions.return_value = [
        subscription_factory(Capability.battery),
        subscription_factory(Capability.switch),
        subscription_factory(Capability.switch_level),
    ]
    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 smartthings_mock.subscriptions.call_count == 1
    assert smartthings_mock.delete_subscription.call_count == 1
    assert smartthings_mock.create_subscription.call_count == 1