"""BleBox light entities implementation."""

import logging
import math
from typing import Any

import blebox_uniapi.light
from blebox_uniapi.light import BleboxColorMode

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP_KELVIN,
    ATTR_EFFECT,
    ATTR_RGB_COLOR,
    ATTR_RGBW_COLOR,
    ATTR_RGBWW_COLOR,
    ColorMode,
    LightEntity,
    LightEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import BleBoxConfigEntry
from .const import DOMAIN, LIGHT_MAX_KELVINS, LIGHT_MIN_KELVINS
from .coordinator import BleBoxCoordinator
from .entity import BleBoxEntity
from .util import blebox_command

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 1


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: BleBoxConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up a BleBox entry."""
    coordinator = config_entry.runtime_data
    entities = [
        BleBoxLightEntity(coordinator, feature)
        for feature in coordinator.box.features.get("lights", [])
    ]
    async_add_entities(entities)


COLOR_MODE_MAP = {
    BleboxColorMode.RGBW: ColorMode.RGBW,
    BleboxColorMode.RGB: ColorMode.RGB,
    BleboxColorMode.MONO: ColorMode.BRIGHTNESS,
    BleboxColorMode.RGBorW: ColorMode.RGBW,  # white hex is prioritised over RGB channel
    BleboxColorMode.CT: ColorMode.COLOR_TEMP,
    BleboxColorMode.CTx2: ColorMode.COLOR_TEMP,  # two instances
    BleboxColorMode.RGBWW: ColorMode.RGBWW,
}


class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
    """Representation of BleBox lights."""

    _attr_min_color_temp_kelvin = LIGHT_MIN_KELVINS
    _attr_max_color_temp_kelvin = LIGHT_MAX_KELVINS

    def __init__(
        self, coordinator: BleBoxCoordinator, feature: blebox_uniapi.light.Light
    ) -> None:
        """Initialize a BleBox light."""
        super().__init__(coordinator, feature)
        if feature.effect_list:
            self._attr_supported_features = LightEntityFeature.EFFECT

    @property
    def is_on(self) -> bool:
        """Return if light is on."""
        return self._feature.is_on

    @property
    def brightness(self) -> int | None:
        """Return the name."""
        return self._feature.brightness

    def _color_temp_to_native_scale(self, x: int) -> int:
        """Convert color temperature from Kelvin to native BleBox scale (0-255).

        BleBox native scale is inverted:
        0=warm (2700K), 255=cold (6500K).
        """
        scaled = (
            (self._attr_max_color_temp_kelvin - x)
            / (self._attr_max_color_temp_kelvin - self._attr_min_color_temp_kelvin)
        ) * 255
        # note: within the operating temperature range here the Kelvin
        # scale has less "integer steps" than the native scale used
        # by blebox devices. Thus we need to use rounding method that is opposite
        # to the one used in _color_temp_from_native_scale in order to avoid
        # temperature value jumping by one step when the temperature value is read
        # back from the device
        bounded = max(min(math.floor(scaled), 255), 0)
        return int(bounded)

    def _color_temp_from_native_scale(self, x: int) -> int:
        """Convert color temperature from native BleBox scale (0-255) to Kelvin.

        BleBox native scale is inverted:
        0=warm (2700K), 255=cold (6500K).
        """
        scaled = self._attr_max_color_temp_kelvin - (x / 255) * (
            self._attr_max_color_temp_kelvin - self._attr_min_color_temp_kelvin
        )
        # note: see _color_temp_to_native_scale for explanation of rounding method
        bounded = max(
            min(math.ceil(scaled), self._attr_max_color_temp_kelvin),
            self._attr_min_color_temp_kelvin,
        )
        return int(bounded)

    @property
    def color_temp_kelvin(self) -> int:
        """Return the color temperature value in Kelvin."""
        return self._color_temp_from_native_scale(self._feature.color_temp)

    @property
    def color_mode(self) -> ColorMode:
        """Return the color mode.

        Set values to _attr_ibutes if needed.
        """
        return COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)

    @property
    def supported_color_modes(self) -> set[ColorMode]:
        """Return supported color modes."""
        return {self.color_mode}

    @property
    def effect_list(self) -> list[str]:
        """Return the list of supported effects."""
        return self._feature.effect_list

    @property
    def effect(self) -> str | None:
        """Return the current effect."""
        return self._feature.effect

    @property
    def rgb_color(self) -> tuple[int, int, int] | None:
        """Return value for rgb."""
        if (rgb_hex := self._feature.rgb_hex) is None:
            return None
        return tuple(
            blebox_uniapi.light.Light.normalise_elements_of_rgb(
                blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgb_hex)[0:3]
            )
        )

    @property
    def rgbw_color(self) -> tuple[int, int, int, int] | None:
        """Return the hue and saturation."""
        if (rgbw_hex := self._feature.rgbw_hex) is None:
            return None
        return tuple(blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgbw_hex)[0:4])

    @property
    def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
        """Return value for rgbww."""
        if (rgbww_hex := self._feature.rgbww_hex) is None:
            return None
        return tuple(blebox_uniapi.light.Light.rgb_hex_to_rgb_list(rgbww_hex))

    @blebox_command
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""

        rgbw = kwargs.get(ATTR_RGBW_COLOR)
        brightness = kwargs.get(ATTR_BRIGHTNESS)
        effect = kwargs.get(ATTR_EFFECT)
        color_temp_kelvin = kwargs.get(ATTR_COLOR_TEMP_KELVIN)
        rgbww = kwargs.get(ATTR_RGBWW_COLOR)
        rgb = kwargs.get(ATTR_RGB_COLOR)

        feature = self._feature
        value = feature.sensible_on_value

        if rgbw is not None:
            value = list(rgbw)
        if color_temp_kelvin is not None:
            value = feature.return_color_temp_with_brightness(
                self._color_temp_to_native_scale(color_temp_kelvin),
                self.brightness,
            )

        if rgbww is not None:
            value = list(rgbww)

        if rgb is not None:
            if self.color_mode == ColorMode.RGB and brightness is None:
                brightness = self.brightness
            value = list(rgb)

        if brightness is not None:
            if self.color_mode == ColorMode.COLOR_TEMP:
                value = feature.return_color_temp_with_brightness(
                    self._color_temp_to_native_scale(self.color_temp_kelvin),
                    brightness,
                )
            else:
                value = feature.apply_brightness(value, brightness)

        if isinstance(value, (list, tuple)) and not any(value):
            await self._feature.async_off()
            return

        try:
            await self._feature.async_on(value)
        except ValueError as exc:
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="bad_value",
                translation_placeholders={"error": str(exc)},
            ) from exc

        if effect is not None:
            try:
                effect_value = self.effect_list.index(effect)
                await self._feature.async_api_command("effect", effect_value)
            except ValueError as exc:
                raise HomeAssistantError(
                    translation_domain=DOMAIN,
                    translation_key="effect_not_found",
                    translation_placeholders={"error": str(exc)},
                ) from exc

    @blebox_command
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the light off."""
        await self._feature.async_off()
