"""Support for LetPot select entities."""

from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from enum import StrEnum
from typing import Any, override

from letpot.deviceclient import LetPotDeviceClient
from letpot.models import (
    DeviceFeature,
    LetPotDeviceStatus,
    LetPotGardenStatus,
    LightMode,
    TemperatureUnit,
)

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import LetPotConfigEntry, LetPotDeviceCoordinator
from .entity import LetPotEntity, LetPotEntityDescription, exception_handler

# Each change pushes a 'full' device status with the change. The library will cache
# pending changes to avoid overwriting, but try to avoid a lot of parallelism.
PARALLEL_UPDATES = 1


class LightBrightnessLowHigh(StrEnum):
    """Light brightness low/high model."""

    LOW = "low"
    HIGH = "high"


def _get_brightness_low_high_value(coordinator: LetPotDeviceCoordinator) -> str | None:
    """Return brightness as low/high for a device with two levels."""
    brightness = coordinator.data.light_brightness
    levels = coordinator.device_client.get_light_brightness_levels(
        coordinator.device.serial_number
    )
    return (
        LightBrightnessLowHigh.LOW.value
        if levels[0] == brightness
        else LightBrightnessLowHigh.HIGH.value
    )


async def _set_brightness_low_high_value(
    device_client: LetPotDeviceClient, serial: str, option: str
) -> None:
    """Set brightness from low/high for a device which only has a low and high value."""
    levels = device_client.get_light_brightness_levels(serial)
    await device_client.set_light_brightness(
        serial, levels[0] if option == LightBrightnessLowHigh.LOW.value else levels[1]
    )


@dataclass(frozen=True, kw_only=True)
class LetPotSelectEntityDescription[_DataT: LetPotDeviceStatus](
    LetPotEntityDescription, SelectEntityDescription
):
    """Describes a LetPot select entity."""

    value_fn: Callable[[LetPotDeviceCoordinator[_DataT]], str | None]
    set_value_fn: Callable[[LetPotDeviceClient, str, str], Coroutine[Any, Any, None]]


SELECTORS: tuple[LetPotSelectEntityDescription[LetPotGardenStatus], ...] = (
    LetPotSelectEntityDescription[LetPotGardenStatus](
        key="display_temperature_unit",
        translation_key="display_temperature_unit",
        options=[x.name.lower() for x in TemperatureUnit],
        value_fn=(
            lambda coordinator: (
                coordinator.data.temperature_unit.name.lower()
                if coordinator.data.temperature_unit is not None
                else None
            )
        ),
        set_value_fn=(
            lambda device_client, serial, option: device_client.set_temperature_unit(
                serial, TemperatureUnit[option.upper()]
            )
        ),
        supported_fn=(
            lambda coordinator: (
                DeviceFeature.TEMPERATURE_SET_UNIT
                in coordinator.device_client.device_info(
                    coordinator.device.serial_number
                ).features
            )
        ),
        entity_category=EntityCategory.CONFIG,
    ),
    LetPotSelectEntityDescription[LetPotGardenStatus](
        key="light_brightness_low_high",
        translation_key="light_brightness",
        options=[
            LightBrightnessLowHigh.LOW.value,
            LightBrightnessLowHigh.HIGH.value,
        ],
        value_fn=_get_brightness_low_high_value,
        set_value_fn=_set_brightness_low_high_value,
        supported_fn=(
            lambda coordinator: (
                DeviceFeature.LIGHT_BRIGHTNESS_LOW_HIGH
                in coordinator.device_client.device_info(
                    coordinator.device.serial_number
                ).features
            )
        ),
        entity_category=EntityCategory.CONFIG,
    ),
    LetPotSelectEntityDescription[LetPotGardenStatus](
        key="light_mode",
        translation_key="light_mode",
        options=[x.name.lower() for x in LightMode],
        value_fn=(
            lambda coordinator: (
                coordinator.data.light_mode.name.lower()
                if coordinator.data.light_mode is not None
                else None
            )
        ),
        set_value_fn=(
            lambda device_client, serial, option: device_client.set_light_mode(
                serial, LightMode[option.upper()]
            )
        ),
        entity_category=EntityCategory.CONFIG,
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: LetPotConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up LetPot select entities."""
    coordinators = entry.runtime_data
    async_add_entities(
        LetPotSelectEntity[LetPotGardenStatus](coordinator, description)
        for description in SELECTORS
        for coordinator in coordinators
        if description.supported_fn(coordinator)
    )


class LetPotSelectEntity[_DataT: LetPotDeviceStatus](
    LetPotEntity[_DataT], SelectEntity
):
    """Defines a LetPot select entity."""

    entity_description: LetPotSelectEntityDescription[_DataT]

    def __init__(
        self,
        coordinator: LetPotDeviceCoordinator[_DataT],
        description: LetPotSelectEntityDescription[_DataT],
    ) -> None:
        """Initialize LetPot select entity."""
        super().__init__(coordinator)
        self.entity_description = description
        self._attr_unique_id = (
            f"{coordinator.config_entry.unique_id}"
            f"_{coordinator.device.serial_number}"
            f"_{description.key}"
        )

    @property
    @override
    def current_option(self) -> str | None:
        """Return the selected entity option."""
        return self.entity_description.value_fn(self.coordinator)

    @exception_handler
    @override
    async def async_select_option(self, option: str) -> None:
        """Change the selected option."""
        return await self.entity_description.set_value_fn(
            self.coordinator.device_client,
            self.coordinator.device.serial_number,
            option,
        )
