"""Tests for the Broadlink radio_frequency platform."""

from datetime import timedelta
from unittest.mock import MagicMock, call

from broadlink.exceptions import BroadlinkException
import pytest
from rf_protocols.commands.ook import OOKCommand

from homeassistant.components import radio_frequency
from homeassistant.components.broadlink.const import DOMAIN
from homeassistant.components.broadlink.radio_frequency import (
    _RF_315_TYPE_BYTE,
    _RF_433_TYPE_BYTE,
    _TICK_US,
    encode_rf_packet,
)
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import dt as dt_util

from . import get_device

from tests.common import async_fire_time_changed

_FREQ_433 = 433_920_000
_FREQ_315 = 315_000_000


async def _setup_rf_device(hass: HomeAssistant) -> tuple[MagicMock, str]:
    """Set up the RMPRO test device, return its api mock and RF entity_id."""
    device = get_device("Office")
    mock_setup = await device.setup_entry(hass)
    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)
    device_entry = device_registry.async_get_device(
        identifiers={(DOMAIN, mock_setup.entry.unique_id)}
    )
    entries = er.async_entries_for_device(entity_registry, device_entry.id)
    rf_entity = next(e for e in entries if e.domain == Platform.RADIO_FREQUENCY)
    return mock_setup.api, rf_entity.entity_id


@pytest.mark.parametrize(
    ("device_name", "has_rf"),
    [
        ("Office", True),  # RMPRO
        ("Garage", True),  # RM4PRO
        ("Entrance", False),  # RMMINI
    ],
)
async def test_radio_frequency_setup(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    device_registry: dr.DeviceRegistry,
    device_name: str,
    has_rf: bool,
) -> None:
    """RF entity is created only for RF-capable devices."""
    device = get_device(device_name)
    mock_setup = await device.setup_entry(hass)
    device_entry = device_registry.async_get_device(
        identifiers={(DOMAIN, mock_setup.entry.unique_id)}
    )
    entries = er.async_entries_for_device(entity_registry, device_entry.id)
    rf_entities = [e for e in entries if e.domain == Platform.RADIO_FREQUENCY]
    assert len(rf_entities) == (1 if has_rf else 0)


def test_encode_rf_packet() -> None:
    """Pulses are encoded inline below 256 ticks, escape-prefixed above."""
    timings = [round(12 * _TICK_US), round(300 * _TICK_US), round(12 * _TICK_US)]
    packet = encode_rf_packet(
        type_byte=_RF_433_TYPE_BYTE, repeat_count=3, timings_us=timings
    )
    # type byte, repeat count, payload length (le16), 12, escape (00 01 2c), 12
    assert packet == bytes([0xB2, 0x03, 0x05, 0x00, 0x0C, 0x00, 0x01, 0x2C, 0x0C])


async def test_send_command(hass: HomeAssistant) -> None:
    """An OOK command transmits the encoded packet once."""
    api, entity_id = await _setup_rf_device(hass)

    timings = [400, -800, 400, -800]
    command = OOKCommand(frequency=_FREQ_433, timings=timings)
    await radio_frequency.async_send_command(hass, entity_id, command)

    expected = encode_rf_packet(
        type_byte=_RF_433_TYPE_BYTE, repeat_count=0, timings_us=timings
    )
    assert api.send_data.call_args == call(expected)


async def test_send_command_315_band(hass: HomeAssistant) -> None:
    """A 315 MHz command uses the 0xB4 type byte."""
    api, entity_id = await _setup_rf_device(hass)

    command = OOKCommand(frequency=_FREQ_315, timings=[400, -800])
    await radio_frequency.async_send_command(hass, entity_id, command)

    assert api.send_data.call_args.args[0][0] == _RF_315_TYPE_BYTE


async def test_send_command_rejects_out_of_band(hass: HomeAssistant) -> None:
    """An out-of-band frequency is rejected before send."""
    api, entity_id = await _setup_rf_device(hass)

    command = OOKCommand(frequency=868_000_000, timings=[400, -800])
    with pytest.raises(HomeAssistantError):
        await radio_frequency.async_send_command(hass, entity_id, command)
    api.send_data.assert_not_called()


async def test_send_command_transmit_failure(hass: HomeAssistant) -> None:
    """A broadlink exception surfaces as HomeAssistantError."""
    api, entity_id = await _setup_rf_device(hass)
    api.send_data.side_effect = BroadlinkException("nope")

    command = OOKCommand(frequency=_FREQ_433, timings=[400, -800])
    with pytest.raises(HomeAssistantError):
        await radio_frequency.async_send_command(hass, entity_id, command)


async def test_entity_availability(hass: HomeAssistant) -> None:
    """Entity becomes unavailable when the device stops responding."""
    api, entity_id = await _setup_rf_device(hass)
    assert hass.states.get(entity_id).state != STATE_UNAVAILABLE

    api.check_sensors.side_effect = OSError("disconnected")
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
    await hass.async_block_till_done()

    assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
