"""Tests for wemo_device.py."""
import asyncio
from dataclasses import asdict
from datetime import timedelta
from unittest.mock import call, patch

import pytest
from pywemo.exceptions import ActionException, PyWeMoException
from pywemo.subscribe import EVENT_TYPE_LONG_PRESS

from homeassistant import runner
from homeassistant.components.wemo import CONF_DISCOVERY, CONF_STATIC, wemo_device
from homeassistant.components.wemo.const import DOMAIN, WEMO_SUBSCRIPTION_EVENT
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow

from .conftest import MOCK_FIRMWARE_VERSION, MOCK_HOST, MOCK_SERIAL_NUMBER

from tests.common import async_fire_time_changed

asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(True))


@pytest.fixture
def pywemo_model():
    """Pywemo LightSwitch models use the switch platform."""
    return "LightSwitchLongPress"


async def test_async_register_device_longpress_fails(
    hass: HomeAssistant, pywemo_device, device_registry: dr.DeviceRegistry
) -> None:
    """Device is still registered if ensure_long_press_virtual_device fails."""
    with patch.object(pywemo_device, "ensure_long_press_virtual_device") as elp:
        elp.side_effect = PyWeMoException
        assert await async_setup_component(
            hass,
            DOMAIN,
            {
                DOMAIN: {
                    CONF_DISCOVERY: False,
                    CONF_STATIC: [MOCK_HOST],
                },
            },
        )
        await hass.async_block_till_done()
    device_entries = list(device_registry.devices.values())
    assert len(device_entries) == 1
    device = wemo_device.async_get_coordinator(hass, device_entries[0].id)
    assert device.supports_long_press is False


async def test_long_press_event(
    hass: HomeAssistant, pywemo_registry, wemo_entity
) -> None:
    """Device fires a long press event."""
    device = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
    got_event = asyncio.Event()
    event_data = {}

    @callback
    def async_event_received(event):
        nonlocal event_data
        event_data = event.data
        got_event.set()

    hass.bus.async_listen_once(WEMO_SUBSCRIPTION_EVENT, async_event_received)

    await hass.async_add_executor_job(
        pywemo_registry.callbacks[device.wemo.name],
        device.wemo,
        EVENT_TYPE_LONG_PRESS,
        "testing_params",
    )

    async with asyncio.timeout(8):
        await got_event.wait()

    assert event_data == {
        "device_id": wemo_entity.device_id,
        "name": device.wemo.name,
        "params": "testing_params",
        "type": EVENT_TYPE_LONG_PRESS,
        "unique_id": device.wemo.serial_number,
    }


async def test_subscription_callback(
    hass: HomeAssistant, pywemo_registry, wemo_entity
) -> None:
    """Device processes a registry subscription callback."""
    device = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
    device.last_update_success = False

    got_callback = asyncio.Event()

    @callback
    def async_received_callback():
        got_callback.set()

    device.async_add_listener(async_received_callback)

    await hass.async_add_executor_job(
        pywemo_registry.callbacks[device.wemo.name], device.wemo, "", ""
    )

    async with asyncio.timeout(8):
        await got_callback.wait()
    assert device.last_update_success


async def test_subscription_update_action_exception(
    hass: HomeAssistant, pywemo_device, wemo_entity
) -> None:
    """Device handles ActionException on get_state properly."""
    device = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
    device.last_update_success = True

    pywemo_device.subscription_update.return_value = False
    pywemo_device.get_state.reset_mock()
    pywemo_device.get_state.side_effect = ActionException
    await hass.async_add_executor_job(
        device.subscription_callback, pywemo_device, "", ""
    )
    await hass.async_block_till_done()

    pywemo_device.get_state.assert_called_once_with(True)
    assert device.last_update_success is False
    assert isinstance(device.last_exception, UpdateFailed)


async def test_subscription_update_exception(
    hass: HomeAssistant, pywemo_device, wemo_entity
) -> None:
    """Device handles Exception on get_state properly."""
    device = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
    device.last_update_success = True

    pywemo_device.subscription_update.return_value = False
    pywemo_device.get_state.reset_mock()
    pywemo_device.get_state.side_effect = Exception
    await hass.async_add_executor_job(
        device.subscription_callback, pywemo_device, "", ""
    )
    await hass.async_block_till_done()

    pywemo_device.get_state.assert_called_once_with(True)
    assert device.last_update_success is False
    assert isinstance(device.last_exception, Exception)


async def test_async_update_data_subscribed(
    hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity
) -> None:
    """No update happens when the device is subscribed."""
    device = wemo_device.async_get_coordinator(hass, wemo_entity.device_id)
    pywemo_registry.is_subscribed.return_value = True
    pywemo_device.get_state.reset_mock()
    await device._async_update_data()
    pywemo_device.get_state.assert_not_called()


async def test_device_info(
    hass: HomeAssistant, wemo_entity, device_registry: dr.DeviceRegistry
) -> None:
    """Verify the DeviceInfo data is set properly."""
    device_entries = list(device_registry.devices.values())

    assert len(device_entries) == 1
    assert device_entries[0].connections == {
        ("upnp", f"uuid:LightSwitch-1_0-{MOCK_SERIAL_NUMBER}")
    }
    assert device_entries[0].manufacturer == "Belkin"
    assert device_entries[0].model == "LightSwitch"
    assert device_entries[0].sw_version == MOCK_FIRMWARE_VERSION


async def test_dli_device_info(
    hass: HomeAssistant, wemo_dli_entity, device_registry: dr.DeviceRegistry
) -> None:
    """Verify the DeviceInfo data for Digital Loggers emulated wemo device."""
    device_entries = list(device_registry.devices.values())

    assert device_entries[0].configuration_url == "http://127.0.0.1"
    assert device_entries[0].identifiers == {(DOMAIN, "123456789")}


async def test_options_enable_subscription_false(
    hass, pywemo_registry, pywemo_device, wemo_entity
):
    """Test setting Options.enable_subscription = False."""
    config_entry = hass.config_entries.async_get_entry(wemo_entity.config_entry_id)
    assert hass.config_entries.async_update_entry(
        config_entry,
        options=asdict(
            wemo_device.Options(enable_subscription=False, enable_long_press=False)
        ),
    )
    await hass.async_block_till_done()
    pywemo_registry.unregister.assert_called_once_with(pywemo_device)


async def test_options_enable_long_press_false(hass, pywemo_device, wemo_entity):
    """Test setting Options.enable_long_press = False."""
    config_entry = hass.config_entries.async_get_entry(wemo_entity.config_entry_id)
    assert hass.config_entries.async_update_entry(
        config_entry, options=asdict(wemo_device.Options(enable_long_press=False))
    )
    await hass.async_block_till_done()
    pywemo_device.remove_long_press_virtual_device.assert_called_once_with()


class TestInsight:
    """Tests specific to the WeMo Insight device."""

    @pytest.fixture
    def pywemo_model(self):
        """Pywemo Dimmer models use the light platform (WemoDimmer class)."""
        return "Insight"

    @pytest.fixture(name="pywemo_device")
    def pywemo_device_fixture(self, pywemo_device):
        """Fixture for WeMoDevice instances."""
        pywemo_device.insight_params = {
            "currentpower": 1.0,
            "todaymw": 200000000.0,
            "state": 0,
            "onfor": 0,
            "ontoday": 0,
            "ontotal": 0,
            "powerthreshold": 0,
        }
        return pywemo_device

    @pytest.mark.parametrize(
        ("subscribed", "state", "expected_calls"),
        [
            (False, 0, [call(), call(True), call(), call()]),
            (False, 1, [call(), call(True), call(), call()]),
            (True, 0, [call(), call(True), call(), call()]),
            (True, 1, [call(), call(), call()]),
        ],
    )
    async def test_should_poll(
        self,
        hass,
        subscribed,
        state,
        expected_calls,
        wemo_entity,
        pywemo_device,
        pywemo_registry,
    ):
        """Validate the should_poll returns the correct value."""
        pywemo_registry.is_subscribed.return_value = subscribed
        pywemo_device.get_state.reset_mock()
        pywemo_device.get_state.return_value = state
        async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
        await hass.async_block_till_done()
        pywemo_device.get_state.assert_has_calls(expected_calls)
