"""Support for Xiaomi Philips Lights."""

import datetime
from datetime import timedelta
from functools import partial
import logging
from math import ceil
from typing import Any, override

from miio import (
    Ceil,
    Device as MiioDevice,
    DeviceException,
    PhilipsBulb,
    PhilipsEyecare,
    PhilipsMoonlight,
)
from miio.gateway.devices.light import LightBulb
from miio.gateway.gateway import (
    GATEWAY_MODEL_AC_V1,
    GATEWAY_MODEL_AC_V2,
    GATEWAY_MODEL_AC_V3,
    GatewayException,
)

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP_KELVIN,
    ATTR_HS_COLOR,
    ColorMode,
    LightEntity,
)
from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import color as color_util, dt as dt_util

from .const import (
    ATTR_SCENE,
    CONF_FLOW_TYPE,
    CONF_GATEWAY,
    DOMAIN,
    LIGHT_DATA_KEY as DATA_KEY,
    MODELS_LIGHT_BULB,
    MODELS_LIGHT_CEILING,
    MODELS_LIGHT_EYECARE,
    MODELS_LIGHT_MONO,
    MODELS_LIGHT_MOON,
)
from .entity import XiaomiGatewayDevice, XiaomiMiioEntity
from .typing import XiaomiMiioConfigEntry

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = "Xiaomi Philips Light"

# The light does not accept cct values < 1
CCT_MIN = 1
CCT_MAX = 100

DELAYED_TURN_OFF_MAX_DEVIATION_SECONDS = 4
DELAYED_TURN_OFF_MAX_DEVIATION_MINUTES = 1

SUCCESS = ["ok"]
ATTR_DELAYED_TURN_OFF = "delayed_turn_off"
ATTR_NIGHT_LIGHT_MODE = "night_light_mode"
ATTR_AUTOMATIC_COLOR_TEMPERATURE = "automatic_color_temperature"
ATTR_REMINDER = "reminder"
ATTR_EYECARE_MODE = "eyecare_mode"

# Moonlight
ATTR_SLEEP_ASSISTANT = "sleep_assistant"
ATTR_SLEEP_OFF_TIME = "sleep_off_time"
ATTR_TOTAL_ASSISTANT_SLEEP_TIME = "total_assistant_sleep_time"
ATTR_BAND_SLEEP = "band_sleep"
ATTR_BAND = "band"


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: XiaomiMiioConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Xiaomi light from a config entry."""
    entities: list[LightEntity] = []
    entity: LightEntity
    light: MiioDevice

    if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY:
        gateway = config_entry.runtime_data.gateway
        gateway_coordinators = config_entry.runtime_data.gateway_coordinators
        # Gateway light
        if gateway.model not in [
            GATEWAY_MODEL_AC_V1,
            GATEWAY_MODEL_AC_V2,
            GATEWAY_MODEL_AC_V3,
        ]:
            entities.append(
                XiaomiGatewayLight(gateway, config_entry.title, config_entry.unique_id)
            )
        # Gateway sub devices
        sub_devices = gateway.devices
        entities.extend(
            XiaomiGatewayBulb(gateway_coordinators[sub_device.sid])
            for sub_device in sub_devices.values()
            if sub_device.device_type == "LightBulb"
        )

    if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE:
        if DATA_KEY not in hass.data:
            hass.data[DATA_KEY] = {}

        host = config_entry.data[CONF_HOST]
        token = config_entry.data[CONF_TOKEN]
        name = config_entry.title
        model = config_entry.data[CONF_MODEL]
        unique_id = config_entry.unique_id

        _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])

        if model in MODELS_LIGHT_EYECARE:
            light = PhilipsEyecare(host, token)
            entity = XiaomiPhilipsEyecareLamp(name, light, config_entry, unique_id)
            entities.append(entity)
            hass.data[DATA_KEY][host] = entity

            entities.append(
                XiaomiPhilipsEyecareLampAmbientLight(
                    name, light, config_entry, unique_id
                )
            )
            # The ambient light doesn't expose additional services.
            # A hass.data[DATA_KEY] entry isn't needed.
        elif model in MODELS_LIGHT_CEILING:
            light = Ceil(host, token)
            entity = XiaomiPhilipsCeilingLamp(name, light, config_entry, unique_id)
            entities.append(entity)
            hass.data[DATA_KEY][host] = entity
        elif model in MODELS_LIGHT_MOON:
            light = PhilipsMoonlight(host, token)
            entity = XiaomiPhilipsMoonlightLamp(name, light, config_entry, unique_id)
            entities.append(entity)
            hass.data[DATA_KEY][host] = entity
        elif model in MODELS_LIGHT_BULB:
            light = PhilipsBulb(host, token)
            entity = XiaomiPhilipsBulb(name, light, config_entry, unique_id)
            entities.append(entity)
            hass.data[DATA_KEY][host] = entity
        elif model in MODELS_LIGHT_MONO:
            light = PhilipsBulb(host, token)
            entity = XiaomiPhilipsGenericLight(name, light, config_entry, unique_id)
            entities.append(entity)
            hass.data[DATA_KEY][host] = entity
        else:
            _LOGGER.error(
                (
                    "Unsupported device found! Please create an issue at "
                    "https://github.com/syssi/philipslight/issues "
                    "and provide the following data: %s"
                ),
                model,
            )
            return

    async_add_entities(entities, update_before_add=True)


class XiaomiPhilipsAbstractLight(XiaomiMiioEntity, LightEntity):
    """Representation of a Abstract Xiaomi Philips Light."""

    _attr_color_mode = ColorMode.BRIGHTNESS
    _attr_supported_color_modes = {ColorMode.BRIGHTNESS}
    _device: Ceil | PhilipsBulb | PhilipsEyecare | PhilipsMoonlight

    def __init__(
        self,
        name: str,
        device: Ceil | PhilipsBulb | PhilipsEyecare | PhilipsMoonlight,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._attr_extra_state_attributes = {}

    async def _try_command(self, mask_error, func, *args, **kwargs):
        """Call a light command handling error messages."""
        try:
            result = await self.hass.async_add_executor_job(
                partial(func, *args, **kwargs)
            )
        except DeviceException as exc:
            if self._attr_available:
                _LOGGER.error(mask_error, exc)
                self._attr_available = False

            return False

        _LOGGER.debug("Response received from light: %s", result)
        return result == SUCCESS

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""
        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

            _LOGGER.debug("Setting brightness: %s %s%%", brightness, percent_brightness)

            result = await self._try_command(
                "Setting brightness failed: %s",
                self._device.set_brightness,
                percent_brightness,
            )

            if result:
                self._attr_brightness = brightness
        else:
            await self._try_command("Turning the light on failed.", self._device.on)

    @override
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the light off."""
        await self._try_command("Turning the light off failed.", self._device.off)

    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)


class XiaomiPhilipsGenericLight(XiaomiPhilipsAbstractLight):
    """Representation of a Generic Xiaomi Philips Light."""

    _device: Ceil | PhilipsBulb | PhilipsEyecare | PhilipsMoonlight

    def __init__(
        self,
        name: str,
        device: Ceil | PhilipsBulb | PhilipsEyecare | PhilipsMoonlight,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._attr_extra_state_attributes.update(
            {ATTR_SCENE: None, ATTR_DELAYED_TURN_OFF: None}
        )

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)

        delayed_turn_off = self.delayed_turn_off_timestamp(
            state.delay_off_countdown,
            dt_util.utcnow(),
            self._attr_extra_state_attributes[ATTR_DELAYED_TURN_OFF],
        )

        self._attr_extra_state_attributes.update(
            {ATTR_SCENE: state.scene, ATTR_DELAYED_TURN_OFF: delayed_turn_off}
        )

    async def async_set_scene(self, scene: int = 1):
        """Set the fixed scene."""
        await self._try_command(
            "Setting a fixed scene failed.", self._device.set_scene, scene
        )

    async def async_set_delayed_turn_off(self, time_period: timedelta):
        """Set delayed turn off."""
        await self._try_command(
            "Setting the turn off delay failed.",
            self._device.delay_off,  # type: ignore[union-attr]
            time_period.total_seconds(),
        )

    @staticmethod
    def delayed_turn_off_timestamp(
        countdown: int, current: datetime.datetime, previous: datetime.datetime
    ):
        """Update the turn off timestamp only if necessary."""
        if countdown is not None and countdown > 0:
            new = current.replace(microsecond=0) + timedelta(seconds=countdown)

            if previous is None:
                return new

            lower = timedelta(seconds=-DELAYED_TURN_OFF_MAX_DEVIATION_SECONDS)
            upper = timedelta(seconds=DELAYED_TURN_OFF_MAX_DEVIATION_SECONDS)
            diff = previous - new
            if lower < diff < upper:
                return previous

            return new

        return None


class XiaomiPhilipsBulb(XiaomiPhilipsGenericLight):
    """Representation of a Xiaomi Philips Bulb."""

    _attr_color_mode = ColorMode.COLOR_TEMP
    _attr_supported_color_modes = {ColorMode.COLOR_TEMP}
    _device: Ceil | PhilipsBulb | PhilipsMoonlight

    def __init__(
        self,
        name: str,
        device: Ceil | PhilipsBulb | PhilipsMoonlight,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._color_temp: int | None = None

    @property
    def _current_mireds(self):
        """Return the color temperature."""
        return self._color_temp

    @property
    def _min_mireds(self):
        """Return the coldest color_temp that this light supports."""
        return 175

    @property
    def _max_mireds(self):
        """Return the warmest color_temp that this light supports."""
        return 333

    @property
    @override
    def color_temp_kelvin(self) -> int | None:
        """Return the color temperature value in Kelvin."""
        return (
            color_util.color_temperature_mired_to_kelvin(self._color_temp)
            if self._color_temp
            else None
        )

    @property
    @override
    def min_color_temp_kelvin(self) -> int:
        """Return the warmest color_temp_kelvin that this light supports."""
        return color_util.color_temperature_mired_to_kelvin(self._max_mireds)

    @property
    @override
    def max_color_temp_kelvin(self) -> int:
        """Return the coldest color_temp_kelvin that this light supports."""
        return color_util.color_temperature_mired_to_kelvin(self._min_mireds)

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""
        if ATTR_COLOR_TEMP_KELVIN in kwargs:
            color_temp = color_util.color_temperature_kelvin_to_mired(
                kwargs[ATTR_COLOR_TEMP_KELVIN]
            )
            percent_color_temp = self.translate(
                color_temp, self._max_mireds, self._min_mireds, CCT_MIN, CCT_MAX
            )

        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

        if ATTR_BRIGHTNESS in kwargs and ATTR_COLOR_TEMP_KELVIN in kwargs:
            _LOGGER.debug(
                "Setting brightness and color temperature:"
                " %s %s%%, %s mireds, %s%% cct",
                brightness,
                percent_brightness,
                color_temp,
                percent_color_temp,
            )

            result = await self._try_command(
                "Setting brightness and color temperature failed: %s bri, %s cct",
                self._device.set_brightness_and_color_temperature,
                percent_brightness,
                percent_color_temp,
            )

            if result:
                self._color_temp = color_temp
                self._attr_brightness = brightness

        elif ATTR_COLOR_TEMP_KELVIN in kwargs:
            _LOGGER.debug(
                "Setting color temperature: %s mireds, %s%% cct",
                color_temp,
                percent_color_temp,
            )

            result = await self._try_command(
                "Setting color temperature failed: %s cct",
                self._device.set_color_temperature,
                percent_color_temp,
            )

            if result:
                self._color_temp = color_temp

        elif ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

            _LOGGER.debug("Setting brightness: %s %s%%", brightness, percent_brightness)

            result = await self._try_command(
                "Setting brightness failed: %s",
                self._device.set_brightness,
                percent_brightness,
            )

            if result:
                self._attr_brightness = brightness

        else:
            await self._try_command("Turning the light on failed.", self._device.on)

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)
        self._color_temp = self.translate(
            state.color_temperature,
            CCT_MIN,
            CCT_MAX,
            self._max_mireds,
            self._min_mireds,
        )

        delayed_turn_off = self.delayed_turn_off_timestamp(
            state.delay_off_countdown,
            dt_util.utcnow(),
            self._attr_extra_state_attributes[ATTR_DELAYED_TURN_OFF],
        )

        self._attr_extra_state_attributes.update(
            {ATTR_SCENE: state.scene, ATTR_DELAYED_TURN_OFF: delayed_turn_off}
        )

    @staticmethod
    def translate(value, left_min, left_max, right_min, right_max):
        """Map a value from left span to right span."""
        left_span = left_max - left_min
        right_span = right_max - right_min
        value_scaled = float(value - left_min) / float(left_span)
        return int(right_min + (value_scaled * right_span))


class XiaomiPhilipsCeilingLamp(XiaomiPhilipsBulb):
    """Representation of a Xiaomi Philips Ceiling Lamp."""

    _device: Ceil

    def __init__(
        self,
        name: str,
        device: Ceil,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._attr_extra_state_attributes.update(
            {ATTR_NIGHT_LIGHT_MODE: None, ATTR_AUTOMATIC_COLOR_TEMPERATURE: None}
        )

    @property
    @override
    def _min_mireds(self):
        """Return the coldest color_temp that this light supports."""
        return 175

    @property
    @override
    def _max_mireds(self):
        """Return the warmest color_temp that this light supports."""
        return 370

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)
        self._color_temp = self.translate(
            state.color_temperature,
            CCT_MIN,
            CCT_MAX,
            self._max_mireds,
            self._min_mireds,
        )

        delayed_turn_off = self.delayed_turn_off_timestamp(
            state.delay_off_countdown,
            dt_util.utcnow(),
            self._attr_extra_state_attributes[ATTR_DELAYED_TURN_OFF],
        )

        self._attr_extra_state_attributes.update(
            {
                ATTR_SCENE: state.scene,
                ATTR_DELAYED_TURN_OFF: delayed_turn_off,
                ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
                ATTR_AUTOMATIC_COLOR_TEMPERATURE: state.automatic_color_temperature,
            }
        )


class XiaomiPhilipsEyecareLamp(XiaomiPhilipsGenericLight):
    """Representation of a Xiaomi Philips Eyecare Lamp 2."""

    _device: PhilipsEyecare

    def __init__(
        self,
        name: str,
        device: PhilipsEyecare,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._attr_extra_state_attributes.update(
            {ATTR_REMINDER: None, ATTR_NIGHT_LIGHT_MODE: None, ATTR_EYECARE_MODE: None}
        )

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)

        delayed_turn_off = self.delayed_turn_off_timestamp(
            state.delay_off_countdown,
            dt_util.utcnow(),
            self._attr_extra_state_attributes[ATTR_DELAYED_TURN_OFF],
        )

        self._attr_extra_state_attributes.update(
            {
                ATTR_SCENE: state.scene,
                ATTR_DELAYED_TURN_OFF: delayed_turn_off,
                ATTR_REMINDER: state.reminder,
                ATTR_NIGHT_LIGHT_MODE: state.smart_night_light,
                ATTR_EYECARE_MODE: state.eyecare,
            }
        )

    @override
    async def async_set_delayed_turn_off(self, time_period: timedelta):
        """Set delayed turn off."""
        await self._try_command(
            "Setting the turn off delay failed.",
            self._device.delay_off,
            round(time_period.total_seconds() / 60),
        )

    async def async_reminder_on(self):
        """Enable the eye fatigue notification."""
        await self._try_command(
            "Turning on the reminder failed.", self._device.reminder_on
        )

    async def async_reminder_off(self):
        """Disable the eye fatigue notification."""
        await self._try_command(
            "Turning off the reminder failed.", self._device.reminder_off
        )

    async def async_night_light_mode_on(self):
        """Turn the smart night light mode on."""
        await self._try_command(
            "Turning on the smart night light mode failed.",
            self._device.smart_night_light_on,
        )

    async def async_night_light_mode_off(self):
        """Turn the smart night light mode off."""
        await self._try_command(
            "Turning off the smart night light mode failed.",
            self._device.smart_night_light_off,
        )

    async def async_eyecare_mode_on(self):
        """Turn the eyecare mode on."""
        await self._try_command(
            "Turning on the eyecare mode failed.", self._device.eyecare_on
        )

    async def async_eyecare_mode_off(self):
        """Turn the eyecare mode off."""
        await self._try_command(
            "Turning off the eyecare mode failed.", self._device.eyecare_off
        )

    @staticmethod
    @override
    def delayed_turn_off_timestamp(
        countdown: int, current: datetime.datetime, previous: datetime.datetime
    ):
        """Update the turn off timestamp only if necessary."""
        if countdown is not None and countdown > 0:
            new = current.replace(second=0, microsecond=0) + timedelta(
                minutes=countdown
            )

            if previous is None:
                return new

            lower = timedelta(minutes=-DELAYED_TURN_OFF_MAX_DEVIATION_MINUTES)
            upper = timedelta(minutes=DELAYED_TURN_OFF_MAX_DEVIATION_MINUTES)
            diff = previous - new
            if lower < diff < upper:
                return previous

            return new

        return None


class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight):
    """Representation of a Xiaomi Philips Eyecare Lamp Ambient Light."""

    _device: PhilipsEyecare

    def __init__(
        self,
        name: str,
        device: PhilipsEyecare,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        name = f"{name} Ambient Light"
        if unique_id is not None:
            unique_id = f"{unique_id}-ambient"
        super().__init__(name, device, entry, unique_id)

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""
        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

            _LOGGER.debug(
                "Setting brightness of the ambient light: %s %s%%",
                brightness,
                percent_brightness,
            )

            result = await self._try_command(
                "Setting brightness of the ambient failed: %s",
                self._device.set_ambient_brightness,
                percent_brightness,
            )

            if result:
                self._attr_brightness = brightness
        else:
            await self._try_command(
                "Turning the ambient light on failed.", self._device.ambient_on
            )

    @override
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the light off."""
        await self._try_command(
            "Turning the ambient light off failed.", self._device.ambient_off
        )

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.ambient
        self._attr_brightness = ceil((255 / 100.0) * state.ambient_brightness)


class XiaomiPhilipsMoonlightLamp(XiaomiPhilipsBulb):
    """Representation of a Xiaomi Philips Zhirui Bedside Lamp."""

    _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
    _device: PhilipsMoonlight

    def __init__(
        self,
        name: str,
        device: PhilipsMoonlight,
        entry: XiaomiMiioConfigEntry,
        unique_id: str | None,
    ) -> None:
        """Initialize the light device."""
        super().__init__(name, device, entry, unique_id)

        self._attr_extra_state_attributes.pop(ATTR_DELAYED_TURN_OFF)
        self._attr_extra_state_attributes.update(
            {
                ATTR_SLEEP_ASSISTANT: None,
                ATTR_SLEEP_OFF_TIME: None,
                ATTR_TOTAL_ASSISTANT_SLEEP_TIME: None,
                ATTR_BAND_SLEEP: None,
                ATTR_BAND: None,
            }
        )

    @property
    @override
    def _min_mireds(self):
        """Return the coldest color_temp that this light supports."""
        return 153

    @property
    @override
    def _max_mireds(self):
        """Return the warmest color_temp that this light supports."""
        return 588

    @property
    @override
    def color_mode(self) -> ColorMode:
        """Return the color mode of the light."""
        if self.hs_color:
            return ColorMode.HS
        return ColorMode.COLOR_TEMP

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""
        if ATTR_COLOR_TEMP_KELVIN in kwargs:
            color_temp = color_util.color_temperature_kelvin_to_mired(
                kwargs[ATTR_COLOR_TEMP_KELVIN]
            )
            percent_color_temp = self.translate(
                color_temp, self._max_mireds, self._min_mireds, CCT_MIN, CCT_MAX
            )

        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

        if ATTR_HS_COLOR in kwargs:
            hs_color = kwargs[ATTR_HS_COLOR]
            rgb = color_util.color_hs_to_RGB(*hs_color)

        if ATTR_BRIGHTNESS in kwargs and ATTR_HS_COLOR in kwargs:
            _LOGGER.debug(
                "Setting brightness and color: %s %s%%, %s",
                brightness,
                percent_brightness,
                rgb,
            )

            result = await self._try_command(
                "Setting brightness and color failed: %s bri, %s color",
                self._device.set_brightness_and_rgb,
                percent_brightness,
                rgb,
            )

            if result:
                self._attr_hs_color = hs_color
                self._attr_brightness = brightness

        elif ATTR_BRIGHTNESS in kwargs and ATTR_COLOR_TEMP_KELVIN in kwargs:
            _LOGGER.debug(
                (
                    "Setting brightness and color temperature: "
                    "%s %s%%, %s mireds, %s%% cct"
                ),
                brightness,
                percent_brightness,
                color_temp,
                percent_color_temp,
            )

            result = await self._try_command(
                "Setting brightness and color temperature failed: %s bri, %s cct",
                self._device.set_brightness_and_color_temperature,
                percent_brightness,
                percent_color_temp,
            )

            if result:
                self._color_temp = color_temp
                self._attr_brightness = brightness

        elif ATTR_HS_COLOR in kwargs:
            _LOGGER.debug("Setting color: %s", rgb)

            result = await self._try_command(
                "Setting color failed: %s", self._device.set_rgb, rgb
            )

            if result:
                self._attr_hs_color = hs_color

        elif ATTR_COLOR_TEMP_KELVIN in kwargs:
            _LOGGER.debug(
                "Setting color temperature: %s mireds, %s%% cct",
                color_temp,
                percent_color_temp,
            )

            result = await self._try_command(
                "Setting color temperature failed: %s cct",
                self._device.set_color_temperature,
                percent_color_temp,
            )

            if result:
                self._color_temp = color_temp

        elif ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs[ATTR_BRIGHTNESS]
            percent_brightness = ceil(100 * brightness / 255.0)

            _LOGGER.debug("Setting brightness: %s %s%%", brightness, percent_brightness)

            result = await self._try_command(
                "Setting brightness failed: %s",
                self._device.set_brightness,
                percent_brightness,
            )

            if result:
                self._attr_brightness = brightness

        else:
            await self._try_command("Turning the light on failed.", self._device.on)

    @override
    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state = await self.hass.async_add_executor_job(self._device.status)
        except DeviceException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error("Got exception while fetching the state: %s", ex)

            return

        _LOGGER.debug("Got new state: %s", state)
        self._attr_available = True
        self._attr_is_on = state.is_on
        self._attr_brightness = ceil((255 / 100.0) * state.brightness)
        self._color_temp = self.translate(
            state.color_temperature,
            CCT_MIN,
            CCT_MAX,
            self._max_mireds,
            self._min_mireds,
        )
        self._attr_hs_color = color_util.color_RGB_to_hs(*state.rgb)

        self._attr_extra_state_attributes.update(
            {
                ATTR_SCENE: state.scene,
                ATTR_SLEEP_ASSISTANT: state.sleep_assistant,
                ATTR_SLEEP_OFF_TIME: state.sleep_off_time,
                ATTR_TOTAL_ASSISTANT_SLEEP_TIME: state.total_assistant_sleep_time,
                ATTR_BAND_SLEEP: state.brand_sleep,
                ATTR_BAND: state.brand,
            }
        )

    @override
    async def async_set_delayed_turn_off(self, time_period: timedelta):
        """Set delayed turn off. Unsupported."""
        return


class XiaomiGatewayLight(LightEntity):
    """Representation of a gateway device's light."""

    _attr_color_mode = ColorMode.HS
    _attr_supported_color_modes = {ColorMode.HS}

    def __init__(self, gateway_device, gateway_name, gateway_device_id):
        """Initialize the XiaomiGatewayLight."""
        self._gateway = gateway_device
        self._attr_name = f"{gateway_name} Light"
        self._gateway_device_id = gateway_device_id
        self._attr_unique_id = gateway_device_id
        self._attr_available = False
        self._brightness_pct = 100
        self._rgb = (255, 255, 255)
        self._hs = (0, 0)

    @property
    @override
    def device_info(self) -> DeviceInfo:
        """Return the device info of the gateway."""
        return DeviceInfo(
            identifiers={(DOMAIN, self._gateway_device_id)},
        )

    @property
    @override
    def brightness(self) -> int:
        """Return the brightness of this light between 0..255."""
        return int(255 * self._brightness_pct / 100)

    @property
    @override
    def hs_color(self) -> tuple[float, float]:
        """Return the hs color value."""
        return self._hs

    @override
    def turn_on(self, **kwargs: Any) -> None:
        """Turn the light on."""
        if ATTR_HS_COLOR in kwargs:
            rgb = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
        else:
            rgb = self._rgb

        if ATTR_BRIGHTNESS in kwargs:
            brightness_pct = int(100 * kwargs[ATTR_BRIGHTNESS] / 255)
        else:
            brightness_pct = self._brightness_pct

        self._gateway.light.set_rgb(brightness_pct, rgb)

        self.schedule_update_ha_state()

    @override
    def turn_off(self, **kwargs: Any) -> None:
        """Turn the light off."""
        self._gateway.light.set_rgb(0, self._rgb)
        self.schedule_update_ha_state()

    async def async_update(self) -> None:
        """Fetch state from the device."""
        try:
            state_dict = await self.hass.async_add_executor_job(
                self._gateway.light.rgb_status
            )
        except GatewayException as ex:
            if self._attr_available:
                self._attr_available = False
                _LOGGER.error(
                    "Got exception while fetching the gateway light state: %s", ex
                )
            return

        self._attr_available = True
        self._attr_is_on = state_dict["is_on"]

        if self._attr_is_on:
            self._brightness_pct = state_dict["brightness"]
            self._rgb = state_dict["rgb"]
            self._hs = color_util.color_RGB_to_hs(*self._rgb)


class XiaomiGatewayBulb(XiaomiGatewayDevice, LightEntity):
    """Representation of Xiaomi Gateway Bulb."""

    _attr_color_mode = ColorMode.COLOR_TEMP
    _attr_supported_color_modes = {ColorMode.COLOR_TEMP}
    _sub_device: LightBulb

    @property
    @override
    def brightness(self) -> int:
        """Return the brightness of the light."""
        return round((self._sub_device.status["brightness"] * 255) / 100)

    @property
    def _current_mireds(self):
        """Return current color temperature."""
        return self._sub_device.status["color_temp"]

    @property
    @override
    def is_on(self) -> bool:
        """Return true if light is on."""
        return self._sub_device.status["status"] == "on"

    @property
    def _min_mireds(self):
        """Return min cct."""
        return self._sub_device.status["cct_min"]

    @property
    def _max_mireds(self):
        """Return max cct."""
        return self._sub_device.status["cct_max"]

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Instruct the light to turn on."""
        await self.hass.async_add_executor_job(self._sub_device.on)

        if ATTR_COLOR_TEMP_KELVIN in kwargs:
            color_temp = color_util.color_temperature_kelvin_to_mired(
                kwargs[ATTR_COLOR_TEMP_KELVIN]
            )
            await self.hass.async_add_executor_job(
                self._sub_device.set_color_temp, color_temp
            )

        if ATTR_BRIGHTNESS in kwargs:
            brightness = round((kwargs[ATTR_BRIGHTNESS] * 100) / 255)
            await self.hass.async_add_executor_job(
                self._sub_device.set_brightness, brightness
            )

    @override
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Instruct the light to turn off."""
        await self.hass.async_add_executor_job(self._sub_device.off)
