"""The tests for the PG LAB Electronics switch."""

from datetime import timedelta

from homeassistant import config_entries
from homeassistant.components.switch import (
    DOMAIN as SWITCH_DOMAIN,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
)
from homeassistant.const import (
    ATTR_ASSUMED_STATE,
    ATTR_ENTITY_ID,
    STATE_OFF,
    STATE_ON,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util

from .test_common import get_device_discovery_payload, send_discovery_message

from tests.common import async_fire_mqtt_message, async_fire_time_changed
from tests.typing import MqttMockHAClient


async def call_service(hass: HomeAssistant, entity_id, service, **kwargs):
    """Call a service."""
    await hass.services.async_call(
        SWITCH_DOMAIN,
        service,
        {ATTR_ENTITY_ID: entity_id, **kwargs},
        blocking=True,
    )


async def test_available_relay(
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_pglab
) -> None:
    """Check if relay are properly created when two E-Relay boards are connected."""

    payload = get_device_discovery_payload(
        number_of_shutters=0,
        number_of_boards=2,
    )

    await send_discovery_message(hass, payload)

    for i in range(16):
        state = hass.states.get(f"switch.test_relay_{i}")
        assert state.state == STATE_UNKNOWN
        assert not state.attributes.get(ATTR_ASSUMED_STATE)


async def test_change_state_via_mqtt(
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_pglab
) -> None:
    """Test state update via MQTT."""

    payload = get_device_discovery_payload(
        number_of_shutters=0,
        number_of_boards=1,
    )

    await send_discovery_message(hass, payload)

    # Simulate response from the device
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_UNKNOWN
    assert not state.attributes.get(ATTR_ASSUMED_STATE)

    # Turn relay OFF
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "OFF")
    await hass.async_block_till_done()
    state = hass.states.get("switch.test_relay_0")
    assert not state.attributes.get(ATTR_ASSUMED_STATE)
    assert state.state == STATE_OFF

    # Turn relay ON
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "ON")
    await hass.async_block_till_done()
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_ON

    # Turn relay OFF
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "OFF")
    await hass.async_block_till_done()
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_OFF

    # Turn relay ON
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "ON")
    await hass.async_block_till_done()
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_ON


async def test_mqtt_state_by_calling_service(
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_pglab
) -> None:
    """Calling service to turn ON/OFF relay and check mqtt state."""

    payload = get_device_discovery_payload(
        number_of_shutters=0,
        number_of_boards=1,
    )

    await send_discovery_message(hass, payload)

    # Turn relay ON
    await call_service(hass, "switch.test_relay_0", SERVICE_TURN_ON)
    mqtt_mock.async_publish.assert_called_once_with(
        "pglab/test/relay/0/set", "ON", 0, False
    )
    mqtt_mock.async_publish.reset_mock()

    # Turn relay OFF
    await call_service(hass, "switch.test_relay_0", SERVICE_TURN_OFF)
    mqtt_mock.async_publish.assert_called_once_with(
        "pglab/test/relay/0/set", "OFF", 0, False
    )
    mqtt_mock.async_publish.reset_mock()

    # Turn relay ON
    await call_service(hass, "switch.test_relay_3", SERVICE_TURN_ON)
    mqtt_mock.async_publish.assert_called_once_with(
        "pglab/test/relay/3/set", "ON", 0, False
    )
    mqtt_mock.async_publish.reset_mock()

    # Turn relay OFF
    await call_service(hass, "switch.test_relay_3", SERVICE_TURN_OFF)
    mqtt_mock.async_publish.assert_called_once_with(
        "pglab/test/relay/3/set", "OFF", 0, False
    )
    mqtt_mock.async_publish.reset_mock()


async def test_discovery_update(
    hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_pglab
) -> None:
    """Update discovery message and  check if relay are property updated."""

    payload = get_device_discovery_payload(
        device_name="first_test",
        number_of_shutters=0,
        number_of_boards=1,
    )

    await send_discovery_message(hass, payload)

    # test the available relay in the first configuration
    for i in range(8):
        state = hass.states.get(f"switch.first_test_relay_{i}")
        assert state.state == STATE_UNKNOWN
        assert not state.attributes.get(ATTR_ASSUMED_STATE)

    # prepare a new message ... the same device but renamed
    # and with different relay configuration
    payload = get_device_discovery_payload(
        device_name="second_test",
        number_of_shutters=0,
        number_of_boards=2,
    )

    await send_discovery_message(hass, payload)

    # entity id from the old relay configuration should be reused
    for i in range(8):
        state = hass.states.get(f"switch.first_test_relay_{i}")
        assert state.state == STATE_UNKNOWN
        assert not state.attributes.get(ATTR_ASSUMED_STATE)
    for i in range(8):
        assert not hass.states.get(f"switch.second_test_relay_{i}")

    # check new relay
    for i in range(8, 16):
        state = hass.states.get(f"switch.second_test_relay_{i}")
        assert state.state == STATE_UNKNOWN
        assert not state.attributes.get(ATTR_ASSUMED_STATE)


async def test_disable_entity_state_change_via_mqtt(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    mqtt_mock: MqttMockHAClient,
    setup_pglab,
) -> None:
    """Test state update via MQTT of disable entity."""

    payload = get_device_discovery_payload(
        number_of_shutters=0,
        number_of_boards=1,
    )

    await send_discovery_message(hass, payload)

    # Be sure that the entity relay_0 is available
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_UNKNOWN
    assert not state.attributes.get(ATTR_ASSUMED_STATE)

    # Disable entity relay_0
    new_status = entity_registry.async_update_entity(
        "switch.test_relay_0", disabled_by=er.RegistryEntryDisabler.USER
    )

    # Be sure that the entity is disabled
    assert new_status.disabled is True

    # Try to change the state of the disabled relay_0
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "ON")
    await hass.async_block_till_done()

    # Enable entity relay_0
    new_status = entity_registry.async_update_entity(
        "switch.test_relay_0", disabled_by=None
    )

    # Be sure that the entity is enabled
    assert new_status.disabled is False

    async_fire_time_changed(
        hass,
        dt_util.utcnow()
        + timedelta(seconds=config_entries.RELOAD_AFTER_UPDATE_DELAY + 1),
    )
    await hass.async_block_till_done()

    # Re-send the discovery message
    await send_discovery_message(hass, payload)

    # Be sure that the state is not changed
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_UNKNOWN

    # Try again to change the state of the disabled relay_0
    async_fire_mqtt_message(hass, "pglab/test/relay/0/state", "ON")
    await hass.async_block_till_done()

    # Be sure that the state is been updated
    state = hass.states.get("switch.test_relay_0")
    assert state.state == STATE_ON
