"""Blebox switch tests."""

import logging
from unittest.mock import AsyncMock, PropertyMock

import blebox_uniapi
import pytest

from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
    ATTR_DEVICE_CLASS,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    STATE_OFF,
    STATE_ON,
    STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr

from .conftest import (
    async_setup_config_entry,
    async_setup_entities,
    async_setup_entity,
    mock_feature,
    mock_only_feature,
    setup_product_mock,
)

from tests.common import MockConfigEntry


@pytest.fixture(name="switchbox")
def switchbox_fixture():
    """Return a default switchBox switch entity mock."""
    feature = mock_feature(
        "switches",
        blebox_uniapi.switch.Switch,
        unique_id="BleBox-switchBox-1afe34e750b8-0.relay",
        full_name="switchBox-0.relay",
        device_class="relay",
        is_on=False,
        index=0,
    )
    type(feature).name = PropertyMock(return_value=None)
    feature.async_update = AsyncMock()
    product = feature.product
    type(product).name = PropertyMock(return_value="My switch box")
    type(product).model = PropertyMock(return_value="switchBox")
    type(product).product = PropertyMock(return_value="switchBox")
    return (feature, "switch.my_switch_box")


async def test_switchbox_init(
    switchbox, hass: HomeAssistant, device_registry: dr.DeviceRegistry, config
) -> None:
    """Test switch default state."""

    _feature_mock, entity_id = switchbox

    entry = await async_setup_entity(hass, entity_id)
    assert entry.unique_id == "BleBox-switchBox-1afe34e750b8-0.relay"

    state = hass.states.get(entity_id)
    assert state.name == "My switch box"

    assert state.attributes[ATTR_DEVICE_CLASS] == SwitchDeviceClass.SWITCH

    assert state.state == STATE_OFF

    device = device_registry.async_get(entry.device_id)

    assert device.name == "My switch box"
    assert device.identifiers == {("blebox", "abcd0123ef5678")}
    assert device.manufacturer == "BleBox"
    assert device.model == "switchBox"
    assert device.sw_version == "1.23"


async def test_switchbox_update_when_off(switchbox, hass: HomeAssistant) -> None:
    """Test switch updating when off."""

    feature_mock, entity_id = switchbox

    feature_mock.is_on = False
    await async_setup_entity(hass, entity_id)

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


async def test_switchbox_update_when_on(switchbox, hass: HomeAssistant) -> None:
    """Test switch updating when on."""

    feature_mock, entity_id = switchbox

    feature_mock.is_on = True
    await async_setup_entity(hass, entity_id)

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


async def test_switchbox_on(switchbox, hass: HomeAssistant) -> None:
    """Test turning switch on."""

    feature_mock, entity_id = switchbox

    await async_setup_entity(hass, entity_id)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_ON,
        {"entity_id": entity_id},
        blocking=True,
    )

    feature_mock.async_turn_on.assert_called_once_with()


async def test_switchbox_off(switchbox, hass: HomeAssistant) -> None:
    """Test turning switch off."""

    feature_mock, entity_id = switchbox

    await async_setup_entity(hass, entity_id)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_OFF,
        {"entity_id": entity_id},
        blocking=True,
    )

    feature_mock.async_turn_off.assert_called_once_with()


def relay_mock(relay_id=0):
    """Return a default switchBoxD switch entity mock."""

    feature = mock_only_feature(
        blebox_uniapi.switch.Switch,
        unique_id=f"BleBox-switchBoxD-1afe34e750b8-{relay_id}.relay",
        full_name=f"switchBoxD-{relay_id}.relay",
        device_class="relay",
        is_on=None,
        index=relay_id,
    )
    type(feature).name = PropertyMock(return_value=None)
    return feature


@pytest.fixture(name="switchbox_d")
def switchbox_d_fixture():
    """Set up two mocked Switch features representing a switchBoxD."""

    relay1 = relay_mock(0)
    relay2 = relay_mock(1)
    features = [relay1, relay2]

    product = setup_product_mock("switches", features)

    type(product).name = PropertyMock(return_value="My relays")
    type(product).model = PropertyMock(return_value="switchBoxD")
    type(product).product = PropertyMock(return_value="switchBoxD")
    type(product).brand = PropertyMock(return_value="BleBox")
    type(product).firmware_version = PropertyMock(return_value="1.23")
    type(product).unique_id = PropertyMock(return_value="abcd0123ef5678")

    type(relay1).product = product
    type(relay2).product = product

    return (
        features,
        ["switch.my_relays", "switch.my_relays_2"],
    )


async def test_switchbox_d_init(
    switchbox_d, hass: HomeAssistant, device_registry: dr.DeviceRegistry
) -> None:
    """Test switch default state."""

    _feature_mocks, entity_ids = switchbox_d

    entries = await async_setup_entities(hass, entity_ids)

    entry = entries[0]
    assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-0.relay"

    state = hass.states.get(entity_ids[0])
    assert state.name == "My relays"
    assert state.attributes[ATTR_DEVICE_CLASS] == SwitchDeviceClass.SWITCH
    assert state.state == STATE_UNKNOWN

    device = device_registry.async_get(entry.device_id)

    assert device.name == "My relays"
    assert device.identifiers == {("blebox", "abcd0123ef5678")}
    assert device.manufacturer == "BleBox"
    assert device.model == "switchBoxD"
    assert device.sw_version == "1.23"

    entry = entries[1]
    assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-1.relay"

    state = hass.states.get(entity_ids[1])
    assert state.name == "My relays"
    assert state.attributes[ATTR_DEVICE_CLASS] == SwitchDeviceClass.SWITCH
    assert state.state == STATE_UNKNOWN

    device = device_registry.async_get(entry.device_id)

    assert device.name == "My relays"
    assert device.identifiers == {("blebox", "abcd0123ef5678")}
    assert device.manufacturer == "BleBox"
    assert device.model == "switchBoxD"
    assert device.sw_version == "1.23"


async def test_switchbox_d_update_when_off(switchbox_d, hass: HomeAssistant) -> None:
    """Test switch updating when off."""

    feature_mocks, entity_ids = switchbox_d

    feature_mocks[0].is_on = False
    feature_mocks[1].is_on = False
    await async_setup_entities(hass, entity_ids)

    assert hass.states.get(entity_ids[0]).state == STATE_OFF
    assert hass.states.get(entity_ids[1]).state == STATE_OFF


async def test_switchbox_d_update_when_second_off(
    switchbox_d, hass: HomeAssistant
) -> None:
    """Test switch updating when off."""

    feature_mocks, entity_ids = switchbox_d

    feature_mocks[0].is_on = True
    feature_mocks[1].is_on = False
    await async_setup_entities(hass, entity_ids)

    assert hass.states.get(entity_ids[0]).state == STATE_ON
    assert hass.states.get(entity_ids[1]).state == STATE_OFF


async def test_switchbox_d_turn_first_on(switchbox_d, hass: HomeAssistant) -> None:
    """Test turning switch on."""

    feature_mocks, entity_ids = switchbox_d

    await async_setup_entities(hass, entity_ids)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_ON,
        {"entity_id": entity_ids[0]},
        blocking=True,
    )

    feature_mocks[0].async_turn_on.assert_called_once_with()
    feature_mocks[1].async_turn_on.assert_not_called()


async def test_switchbox_d_second_on(switchbox_d, hass: HomeAssistant) -> None:
    """Test turning switch on."""

    feature_mocks, entity_ids = switchbox_d

    await async_setup_entities(hass, entity_ids)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_ON,
        {"entity_id": entity_ids[1]},
        blocking=True,
    )

    feature_mocks[0].async_turn_on.assert_not_called()
    feature_mocks[1].async_turn_on.assert_called_once_with()


async def test_switchbox_d_first_off(switchbox_d, hass: HomeAssistant) -> None:
    """Test turning switch on."""

    feature_mocks, entity_ids = switchbox_d

    await async_setup_entities(hass, entity_ids)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_OFF,
        {"entity_id": entity_ids[0]},
        blocking=True,
    )

    feature_mocks[0].async_turn_off.assert_called_once_with()
    feature_mocks[1].async_turn_off.assert_not_called()


async def test_switchbox_d_second_off(switchbox_d, hass: HomeAssistant) -> None:
    """Test turning switch on."""

    feature_mocks, entity_ids = switchbox_d

    await async_setup_entities(hass, entity_ids)

    await hass.services.async_call(
        "switch",
        SERVICE_TURN_OFF,
        {"entity_id": entity_ids[1]},
        blocking=True,
    )

    feature_mocks[0].async_turn_off.assert_not_called()
    feature_mocks[1].async_turn_off.assert_called_once_with()


async def test_switchbox_with_name(hass: HomeAssistant) -> None:
    """Test that a switch with a feature name uses it as the entity name."""
    feature = mock_feature(
        "switches",
        blebox_uniapi.switch.Switch,
        unique_id="BleBox-switchBoxD-1afe34e750b8-0.relay",
        full_name="switchBoxD-0.relay",
        device_class="relay",
        is_on=False,
        index=0,
    )
    type(feature).name = PropertyMock(return_value="Garden lights")
    feature.async_update = AsyncMock()
    product = feature.product
    type(product).name = PropertyMock(return_value="My switch box")
    type(product).model = PropertyMock(return_value="switchBoxD")
    type(product).product = PropertyMock(return_value="switchBoxD")

    await async_setup_entity(hass, "switch.my_switch_box_garden_lights")
    state = hass.states.get("switch.my_switch_box_garden_lights")
    assert state.name == "My switch box Garden lights"


ALL_SWITCH_FIXTURES = ["switchbox", "switchbox_d"]


@pytest.mark.parametrize("feature", ALL_SWITCH_FIXTURES, indirect=["feature"])
async def test_update_failure(
    feature,
    hass: HomeAssistant,
    config_entry: MockConfigEntry,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test that update failures cause config entry setup retry."""

    caplog.set_level(logging.ERROR)

    feature_mock, entity_id = feature

    if isinstance(feature_mock, list):
        feature_mock = feature_mock[0]
        entity_id = entity_id[0]

    feature_mock.product.async_update_data = AsyncMock(
        side_effect=blebox_uniapi.error.ClientError
    )

    await async_setup_config_entry(hass, config_entry)

    feature_mock.product.async_update_data.assert_called()
    assert config_entry.state is ConfigEntryState.SETUP_RETRY
