"""The tests for the Rfxtrx component."""

from __future__ import annotations

from unittest.mock import ANY, call

import RFXtrx as rfxtrxmod

from homeassistant.components.rfxtrx.const import EVENT_RFXTRX_EVENT
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component

from .conftest import setup_rfx_test_cfg

from tests.typing import WebSocketGenerator

SOME_PROTOCOLS = ["ac", "arc"]


async def test_fire_event(hass: HomeAssistant, rfxtrx) -> None:
    """Test fire event."""
    await setup_rfx_test_cfg(
        hass,
        device="/dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0",
        automatic_add=True,
        devices={
            "0b1100cd0213c7f210010f51": {},
            "0716000100900970": {},
        },
    )

    device_registry: dr.DeviceRegistry = dr.async_get(hass)

    calls = []

    @callback
    def record_event(event):
        """Add recorded event to set."""
        assert event.event_type == "rfxtrx_event"
        calls.append(event.data)

    hass.bus.async_listen(EVENT_RFXTRX_EVENT, record_event)

    await rfxtrx.signal("0b1100cd0213c7f210010f51")
    await rfxtrx.signal("0716000100900970")

    device_id_1 = device_registry.async_get_device(
        identifiers={("rfxtrx", "11", "0", "213c7f2:16")}
    )
    assert device_id_1

    device_id_2 = device_registry.async_get_device(
        identifiers={("rfxtrx", "16", "0", "00:90")}
    )
    assert device_id_2

    assert calls == [
        {
            "packet_type": 17,
            "sub_type": 0,
            "type_string": "AC",
            "id_string": "213c7f2:16",
            "data": "0b1100cd0213c7f210010f51",
            "values": {"Command": "On", "Rssi numeric": 5},
            "device_id": device_id_1.id,
        },
        {
            "packet_type": 22,
            "sub_type": 0,
            "type_string": "Byron SX",
            "id_string": "00:90",
            "data": "0716000100900970",
            "values": {"Command": "Sound 9", "Rssi numeric": 7, "Sound": 9},
            "device_id": device_id_2.id,
        },
    ]


async def test_send(hass: HomeAssistant, rfxtrx) -> None:
    """Test configuration."""
    await setup_rfx_test_cfg(hass, device="/dev/null", devices={})

    await hass.services.async_call(
        "rfxtrx", "send", {"event": "0a520802060101ff0f0269"}, blocking=True
    )

    assert rfxtrx.transport.send.mock_calls == [
        call(bytearray(b"\x0a\x52\x08\x02\x06\x01\x01\xff\x0f\x02\x69"))
    ]


async def test_ws_device_remove(
    hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
    """Test removing a device through device registry."""
    assert await async_setup_component(hass, "config", {})

    device_id = ["11", "0", "213c7f2:16"]
    mock_entry = await setup_rfx_test_cfg(
        hass,
        devices={
            "0b1100cd0213c7f210010f51": {"fire_event": True, "device_id": device_id},
        },
    )

    device_reg = dr.async_get(hass)

    device_entry = device_reg.async_get_device(identifiers={("rfxtrx", *device_id)})
    assert device_entry

    # Ask to remove existing device
    client = await hass_ws_client(hass)
    response = await client.remove_device(device_entry.id, mock_entry.entry_id)
    assert response["success"]

    # Verify device entry is removed
    assert device_reg.async_get_device(identifiers={("rfxtrx", *device_id)}) is None

    # Verify that the config entry has removed the device
    assert mock_entry.data["devices"] == {}


async def test_connect(
    rfxtrx, connect_mock, transport_mock, hass: HomeAssistant
) -> None:
    """Test that we attempt to connect to the device."""

    config_entry = await setup_rfx_test_cfg(hass, device="/dev/ttyUSBfake")
    transport_mock.assert_called_once_with("/dev/ttyUSBfake")
    connect_mock.assert_called_once_with(transport_mock.return_value, ANY, modes=ANY)
    rfxtrx.connect.assert_called_once_with(ANY)

    assert config_entry.state is ConfigEntryState.LOADED


async def test_connect_network(
    rfxtrx, connect_mock, transport_mock, hass: HomeAssistant
) -> None:
    """Test that we attempt to connect to the device."""

    config_entry = await setup_rfx_test_cfg(hass, host="localhost", port=1234)
    transport_mock.assert_called_once_with(("localhost", 1234))
    connect_mock.assert_called_once_with(transport_mock.return_value, ANY, modes=ANY)
    rfxtrx.connect.assert_called_once_with(ANY)

    assert config_entry.state is ConfigEntryState.LOADED


async def test_connect_with_protocols(
    rfxtrx, connect_mock, transport_mock, hass: HomeAssistant
) -> None:
    """Test that we attempt to set protocols."""
    config_entry = await setup_rfx_test_cfg(
        hass, device="/dev/ttyUSBfake", protocols=SOME_PROTOCOLS
    )
    transport_mock.assert_called_once_with("/dev/ttyUSBfake")
    connect_mock.assert_called_once_with(
        transport_mock.return_value, ANY, modes=SOME_PROTOCOLS
    )
    rfxtrx.connect.assert_called_once_with(ANY)

    assert config_entry.state is ConfigEntryState.LOADED


async def test_connect_timeout(
    rfxtrx, connect_mock, transport_mock, hass: HomeAssistant
) -> None:
    """Test that we attempt to connect to the device."""

    rfxtrx.connect.side_effect = TimeoutError

    config_entry = await setup_rfx_test_cfg(hass, device="/dev/ttyUSBfake")
    transport_mock.assert_called_once_with("/dev/ttyUSBfake")
    connect_mock.assert_called_once_with(transport_mock.return_value, ANY, modes=ANY)
    rfxtrx.connect.assert_called_once_with(ANY)

    assert config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_connect_failed(
    rfxtrx, connect_mock, transport_mock, hass: HomeAssistant
) -> None:
    """Test that we attempt to connect to the device."""

    rfxtrx.connect.side_effect = rfxtrxmod.RFXtrxTransportError

    config_entry = await setup_rfx_test_cfg(hass, device="/dev/ttyUSBfake")
    transport_mock.assert_called_once_with("/dev/ttyUSBfake")
    connect_mock.assert_called_once_with(transport_mock.return_value, ANY, modes=ANY)
    rfxtrx.connect.assert_called_once_with(ANY)

    assert config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_reconnect(rfxtrx, hass: HomeAssistant) -> None:
    """Test that we reconnect on connection loss."""
    config_entry = await setup_rfx_test_cfg(hass, device="/dev/ttyUSBfake")

    assert config_entry.state is ConfigEntryState.LOADED
    rfxtrx.connect.call_count = 1

    await hass.async_add_executor_job(
        rfxtrx.event_callback,
        rfxtrxmod.ConnectionLost(),
    )
    await hass.async_block_till_done()

    assert config_entry.state is ConfigEntryState.LOADED
    rfxtrx.connect.call_count = 2
