"""Test the Select entity for HomeWizard."""

from unittest.mock import MagicMock

from homewizard_energy import UnsupportedError
from homewizard_energy.errors import RequestError, UnauthorizedError
from homewizard_energy.models import Batteries
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.homewizard.const import UPDATE_INTERVAL
from homeassistant.components.select import (
    ATTR_OPTION,
    DOMAIN as SELECT_DOMAIN,
    SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
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 tests.common import async_fire_time_changed

pytestmark = [
    pytest.mark.usefixtures("init_integration"),
]


@pytest.mark.parametrize(
    ("device_fixture", "entity_ids"),
    [
        (
            "HWE-WTR",
            [
                "select.device_battery_group_mode",
            ],
        ),
        (
            "SDM230",
            [
                "select.device_battery_group_mode",
            ],
        ),
        (
            "SDM630",
            [
                "select.device_battery_group_mode",
            ],
        ),
        (
            "HWE-KWH1",
            [
                "select.device_battery_group_mode",
            ],
        ),
        (
            "HWE-KWH3",
            [
                "select.device_battery_group_mode",
            ],
        ),
    ],
)
async def test_entities_not_created_for_device(
    hass: HomeAssistant,
    entity_ids: list[str],
) -> None:
    """Ensures entities for a specific device are not created."""
    for entity_id in entity_ids:
        assert not hass.states.get(entity_id)


@pytest.mark.parametrize(
    ("device_fixture", "entity_id"),
    [
        ("HWE-P1", "select.device_battery_group_mode"),
    ],
)
async def test_select_entity_snapshots(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    entity_registry: er.EntityRegistry,
    snapshot: SnapshotAssertion,
    entity_id: str,
) -> None:
    """Test that select entity state and registry entries match snapshots."""
    assert (state := hass.states.get(entity_id))
    assert snapshot == state

    assert (entity_entry := entity_registry.async_get(entity_id))
    assert snapshot == entity_entry

    assert entity_entry.device_id
    assert (device_entry := device_registry.async_get(entity_entry.device_id))
    assert snapshot == device_entry


@pytest.mark.parametrize(
    ("device_fixture", "entity_id", "option", "expected_mode"),
    [
        (
            "HWE-P1",
            "select.device_battery_group_mode",
            "standby",
            Batteries.Mode.STANDBY,
        ),
        (
            "HWE-P1",
            "select.device_battery_group_mode",
            "to_full",
            Batteries.Mode.TO_FULL,
        ),
        ("HWE-P1", "select.device_battery_group_mode", "zero", Batteries.Mode.ZERO),
    ],
)
async def test_select_set_option(
    hass: HomeAssistant,
    mock_homewizardenergy: MagicMock,
    entity_id: str,
    option: str,
    expected_mode: Batteries.Mode,
) -> None:
    """Test that selecting an option calls the correct API method."""
    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_OPTION: option,
        },
        blocking=True,
    )
    mock_homewizardenergy.batteries.assert_called_with(mode=expected_mode)


@pytest.mark.parametrize(
    ("device_fixture", "entity_id", "option"),
    [
        ("HWE-P1", "select.device_battery_group_mode", "zero"),
        ("HWE-P1", "select.device_battery_group_mode", "standby"),
        ("HWE-P1", "select.device_battery_group_mode", "to_full"),
    ],
)
async def test_select_request_error(
    hass: HomeAssistant,
    mock_homewizardenergy: MagicMock,
    entity_id: str,
    option: str,
) -> None:
    """Test that RequestError is handled and raises HomeAssistantError."""
    mock_homewizardenergy.batteries.side_effect = RequestError
    with pytest.raises(
        HomeAssistantError,
        match=r"^An error occurred while communicating with your HomeWizard device$",
    ):
        await hass.services.async_call(
            SELECT_DOMAIN,
            SERVICE_SELECT_OPTION,
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_OPTION: option,
            },
            blocking=True,
        )


@pytest.mark.parametrize(
    ("device_fixture", "entity_id", "option"),
    [
        ("HWE-P1", "select.device_battery_group_mode", "to_full"),
    ],
)
async def test_select_unauthorized_error(
    hass: HomeAssistant,
    mock_homewizardenergy: MagicMock,
    entity_id: str,
    option: str,
) -> None:
    """Test that UnauthorizedError is handled and raises HomeAssistantError."""
    mock_homewizardenergy.batteries.side_effect = UnauthorizedError
    with pytest.raises(
        HomeAssistantError,
        match=(
            r"^The local API is unauthorized\. Restore API"
            r" access by following the instructions in the"
            r" repair issue$"
        ),
    ):
        await hass.services.async_call(
            SELECT_DOMAIN,
            SERVICE_SELECT_OPTION,
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_OPTION: option,
            },
            blocking=True,
        )


@pytest.mark.parametrize("device_fixture", ["HWE-P1"])
@pytest.mark.parametrize("exception", [RequestError, UnsupportedError])
@pytest.mark.parametrize(
    ("entity_id", "method"),
    [
        ("select.device_battery_group_mode", "combined"),
    ],
)
async def test_select_unreachable(
    hass: HomeAssistant,
    mock_homewizardenergy: MagicMock,
    exception: Exception,
    entity_id: str,
    method: str,
) -> None:
    """Test that unreachable devices are marked as unavailable."""
    mocked_method = getattr(mock_homewizardenergy, method)
    mocked_method.side_effect = exception
    async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
    await hass.async_block_till_done()

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


@pytest.mark.parametrize(
    ("device_fixture", "entity_id"),
    [
        ("HWE-P1", "select.device_battery_group_mode"),
    ],
)
async def test_select_multiple_state_changes(
    hass: HomeAssistant,
    mock_homewizardenergy: MagicMock,
    entity_id: str,
) -> None:
    """Test changing select state multiple times in sequence."""
    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_OPTION: "zero",
        },
        blocking=True,
    )
    mock_homewizardenergy.batteries.assert_called_with(mode=Batteries.Mode.ZERO)

    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_OPTION: "to_full",
        },
        blocking=True,
    )
    mock_homewizardenergy.batteries.assert_called_with(mode=Batteries.Mode.TO_FULL)

    await hass.services.async_call(
        SELECT_DOMAIN,
        SERVICE_SELECT_OPTION,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_OPTION: "standby",
        },
        blocking=True,
    )
    mock_homewizardenergy.batteries.assert_called_with(mode=Batteries.Mode.STANDBY)


@pytest.mark.parametrize(
    ("device_fixture", "entity_ids"),
    [
        (
            "HWE-P1-no-batteries",
            [
                "select.device_battery_group_mode",
            ],
        ),
    ],
)
async def test_disabled_by_default_selects(
    hass: HomeAssistant, entity_registry: er.EntityRegistry, entity_ids: list[str]
) -> None:
    """Test the disabled by default selects."""
    for entity_id in entity_ids:
        assert not hass.states.get(entity_id)

        assert (entry := entity_registry.async_get(entity_id))
        assert entry.disabled
        assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
