"""Light for Shelly."""

from dataclasses import dataclass
from typing import Any, Final, cast, override

from aioshelly.block_device import Block
from aioshelly.const import MODEL_BULB, RPC_GENERATIONS

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP_KELVIN,
    ATTR_EFFECT,
    ATTR_RGB_COLOR,
    ATTR_RGBW_COLOR,
    ATTR_TRANSITION,
    DOMAIN as LIGHT_DOMAIN,
    ColorMode,
    LightEntity,
    LightEntityDescription,
    LightEntityFeature,
    brightness_supported,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
    BLOCK_MAX_TRANSITION_TIME_MS,
    DUAL_MODE_LIGHT_MODELS,
    KELVIN_MAX_VALUE,
    KELVIN_MIN_VALUE_COLOR,
    KELVIN_MIN_VALUE_WHITE,
    LOGGER,
    MODELS_SUPPORTING_LIGHT_TRANSITION,
    RGBW_MODELS,
    RPC_MIN_TRANSITION_TIME_SEC,
    SHBLB_1_RGB_EFFECTS,
    STANDARD_RGB_EFFECTS,
)
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import (
    BlockEntityDescription,
    RpcEntityDescription,
    ShellyBlockAttributeEntity,
    ShellyRpcAttributeEntity,
    async_setup_entry_block,
    async_setup_entry_rpc,
)
from .utils import (
    async_remove_orphaned_entities,
    brightness_to_percentage,
    get_device_entry_gen,
    get_rpc_channel_name,
    get_rpc_key_id,
    is_block_channel_type_light,
    is_rpc_channel_type_light,
    percentage_to_brightness,
)

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class BlockLightDescription(BlockEntityDescription, LightEntityDescription):
    """Description for a Shelly BLOCK light entity."""


BLOCK_LIGHTS = {
    ("light", "output"): BlockLightDescription(
        key="light|output",
    ),
    ("relay", "output"): BlockLightDescription(
        key="relay|output",
        removal_condition=lambda settings, block: (
            not is_block_channel_type_light(settings, block)
        ),
    ),
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ShellyConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up light entities."""
    if get_device_entry_gen(config_entry) in RPC_GENERATIONS:
        return _async_setup_rpc_entry(hass, config_entry, async_add_entities)

    return _async_setup_block_entry(hass, config_entry, async_add_entities)


@callback
def _async_setup_block_entry(
    hass: HomeAssistant,
    config_entry: ShellyConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up entities for BLOCK device."""
    coordinator = config_entry.runtime_data.block
    assert coordinator

    async_setup_entry_block(
        hass, config_entry, async_add_entities, BLOCK_LIGHTS, BlockShellyLight
    )


class BlockShellyLight(ShellyBlockAttributeEntity, LightEntity):
    """Entity that controls a light on block based Shelly devices."""

    entity_description: BlockLightDescription
    _attr_supported_color_modes: set[ColorMode]

    def __init__(
        self,
        coordinator: ShellyBlockCoordinator,
        block: Block,
        attribute: str,
        description: BlockLightDescription,
    ) -> None:
        """Initialize block light."""
        super().__init__(coordinator, block, attribute, description)
        self.control_result: dict[str, Any] | None = None
        self._attr_name = None  # Main device entity
        self._attr_unique_id: str = f"{coordinator.mac}-{block.description}"
        self._attr_supported_color_modes = set()
        self._attr_min_color_temp_kelvin = KELVIN_MIN_VALUE_WHITE
        self._attr_max_color_temp_kelvin = KELVIN_MAX_VALUE

        if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"):
            self._attr_min_color_temp_kelvin = KELVIN_MIN_VALUE_COLOR
            if coordinator.model in RGBW_MODELS:
                self._attr_supported_color_modes.add(ColorMode.RGBW)
            else:
                self._attr_supported_color_modes.add(ColorMode.RGB)

        if hasattr(block, "colorTemp"):
            self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)

        if not self._attr_supported_color_modes:
            if hasattr(block, "brightness") or hasattr(block, "gain"):
                self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
            else:
                self._attr_supported_color_modes.add(ColorMode.ONOFF)

        if hasattr(block, "effect"):
            self._attr_supported_features |= LightEntityFeature.EFFECT

        if coordinator.model in MODELS_SUPPORTING_LIGHT_TRANSITION:
            self._attr_supported_features |= LightEntityFeature.TRANSITION

    @property
    @override
    def is_on(self) -> bool:
        """If light is on."""
        if self.control_result:
            return cast(bool, self.control_result["ison"])

        return bool(self.block.output)

    @property
    def mode(self) -> str:
        """Return the color mode of the light."""
        if self.control_result and self.control_result.get("mode"):
            return cast(str, self.control_result["mode"])

        if hasattr(self.block, "mode"):
            return cast(str, self.block.mode)

        if (
            hasattr(self.block, "red")
            and hasattr(self.block, "green")
            and hasattr(self.block, "blue")
        ):
            return "color"

        return "white"

    @property
    @override
    def brightness(self) -> int:
        """Return the brightness of this light between 0..255."""
        if self.mode == "color":
            if self.control_result:
                return percentage_to_brightness(self.control_result["gain"])
            return percentage_to_brightness(cast(int, self.block.gain))

        # white mode
        if self.control_result:
            return percentage_to_brightness(self.control_result["brightness"])
        return percentage_to_brightness(cast(int, self.block.brightness))

    @property
    @override
    def color_mode(self) -> ColorMode:
        """Return the color mode of the light."""
        if self.mode == "color":
            if self.coordinator.model in RGBW_MODELS:
                return ColorMode.RGBW
            return ColorMode.RGB

        if hasattr(self.block, "colorTemp"):
            return ColorMode.COLOR_TEMP

        if hasattr(self.block, "brightness") or hasattr(self.block, "gain"):
            return ColorMode.BRIGHTNESS

        return ColorMode.ONOFF

    @property
    @override
    def rgb_color(self) -> tuple[int, int, int]:
        """Return the rgb color value [int, int, int]."""
        if self.control_result:
            red = self.control_result["red"]
            green = self.control_result["green"]
            blue = self.control_result["blue"]
        else:
            red = self.block.red
            green = self.block.green
            blue = self.block.blue
        return (cast(int, red), cast(int, green), cast(int, blue))

    @property
    @override
    def rgbw_color(self) -> tuple[int, int, int, int]:
        """Return the rgbw color value [int, int, int, int]."""
        if self.control_result:
            white = self.control_result["white"]
        else:
            white = self.block.white

        return (*self.rgb_color, cast(int, white))

    @property
    @override
    def color_temp_kelvin(self) -> int:
        """Return the CT color value in kelvin."""
        color_temp = cast(int, self.block.colorTemp)
        if self.control_result:
            color_temp = self.control_result["temp"]

        return min(
            self.max_color_temp_kelvin,
            max(self.min_color_temp_kelvin, color_temp),
        )

    @property
    @override
    def effect_list(self) -> list[str] | None:
        """Return the list of supported effects."""
        if self.coordinator.model == MODEL_BULB:
            return list(SHBLB_1_RGB_EFFECTS.values())

        return list(STANDARD_RGB_EFFECTS.values())

    @property
    @override
    def effect(self) -> str | None:
        """Return the current effect."""
        if self.control_result:
            effect_index = self.control_result["effect"]
        else:
            effect_index = self.block.effect

        if self.coordinator.model == MODEL_BULB:
            return SHBLB_1_RGB_EFFECTS[cast(int, effect_index)]

        return STANDARD_RGB_EFFECTS[cast(int, effect_index)]

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn on light."""
        if self.block.type == "relay":
            self.control_result = await self.set_state(turn="on")
            self.async_write_ha_state()
            return

        set_mode = None
        supported_color_modes = self._attr_supported_color_modes
        params: dict[str, Any] = {"turn": "on"}

        if ATTR_TRANSITION in kwargs:
            params["transition"] = min(
                int(kwargs[ATTR_TRANSITION] * 1000), BLOCK_MAX_TRANSITION_TIME_MS
            )

        if ATTR_BRIGHTNESS in kwargs and brightness_supported(supported_color_modes):
            if hasattr(self.block, "gain"):
                params["gain"] = brightness_to_percentage(kwargs[ATTR_BRIGHTNESS])
            if hasattr(self.block, "brightness"):
                params["brightness"] = brightness_to_percentage(kwargs[ATTR_BRIGHTNESS])

        if (
            ATTR_COLOR_TEMP_KELVIN in kwargs
            and ColorMode.COLOR_TEMP in supported_color_modes
        ):
            # Color temperature change - used only in white mode,
            # switch device mode to white
            color_temp = kwargs[ATTR_COLOR_TEMP_KELVIN]
            set_mode = "white"
            params["temp"] = int(
                min(
                    self.max_color_temp_kelvin,
                    max(self.min_color_temp_kelvin, color_temp),
                )
            )

        if ATTR_RGB_COLOR in kwargs and ColorMode.RGB in supported_color_modes:
            # Color channels change - used only in color mode,
            # switch device mode to color
            set_mode = "color"
            (params["red"], params["green"], params["blue"]) = kwargs[ATTR_RGB_COLOR]

        if ATTR_RGBW_COLOR in kwargs and ColorMode.RGBW in supported_color_modes:
            # Color channels change - used only in color mode,
            # switch device mode to color
            set_mode = "color"
            (params["red"], params["green"], params["blue"], params["white"]) = kwargs[
                ATTR_RGBW_COLOR
            ]

        if ATTR_EFFECT in kwargs and ATTR_COLOR_TEMP_KELVIN not in kwargs:
            # Color effect change - used only in color mode, switch device mode to color
            set_mode = "color"
            if self.coordinator.model == MODEL_BULB:
                effect_dict = SHBLB_1_RGB_EFFECTS
            else:
                effect_dict = STANDARD_RGB_EFFECTS
            if kwargs[ATTR_EFFECT] in effect_dict.values():
                params["effect"] = [
                    k for k, v in effect_dict.items() if v == kwargs[ATTR_EFFECT]
                ][0]
            else:
                LOGGER.error(
                    "Effect '%s' not supported by device %s",
                    kwargs[ATTR_EFFECT],
                    self.coordinator.model,
                )

        if (
            set_mode
            and set_mode != self.mode
            and self.coordinator.model in DUAL_MODE_LIGHT_MODELS
        ):
            params["mode"] = set_mode

        self.control_result = await self.set_state(**params)
        self.async_write_ha_state()

    @override
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn off light."""
        params: dict[str, Any] = {"turn": "off"}

        if ATTR_TRANSITION in kwargs:
            params["transition"] = min(
                int(kwargs[ATTR_TRANSITION] * 1000), BLOCK_MAX_TRANSITION_TIME_MS
            )

        self.control_result = await self.set_state(**params)

        self.async_write_ha_state()

    @callback
    @override
    def _update_callback(self) -> None:
        """When device updates, clear control & mode result that overrides state."""
        self.control_result = None
        super()._update_callback()


@dataclass(frozen=True, kw_only=True)
class RpcLightDescription(RpcEntityDescription, LightEntityDescription):
    """Description for a Shelly RPC light entity."""


class RpcShellyLightBase(ShellyRpcAttributeEntity, LightEntity):
    """Base Entity for RPC based Shelly devices."""

    entity_description: RpcLightDescription
    _component: str = "Light"

    def __init__(
        self,
        coordinator: ShellyRpcCoordinator,
        key: str,
        attribute: str,
        description: RpcEntityDescription,
    ) -> None:
        """Initialize light."""
        super().__init__(coordinator, key, attribute, description)
        self._attr_name = get_rpc_channel_name(coordinator.device, key)
        self._attr_unique_id = f"{coordinator.mac}-{key}"

    @property
    @override
    def is_on(self) -> bool:
        """If light is on."""
        return bool(self.status["output"])

    @property
    @override
    def brightness(self) -> int:
        """Return the brightness of this light between 0..255."""
        return percentage_to_brightness(self.status["brightness"])

    @property
    @override
    def rgb_color(self) -> tuple[int, int, int]:
        """Return the rgb color value [int, int, int]."""
        return cast(tuple, self.status["rgb"])

    @property
    @override
    def rgbw_color(self) -> tuple[int, int, int, int]:
        """Return the rgbw color value [int, int, int, int]."""
        return (*self.status["rgb"], self.status["white"])

    @property
    @override
    def color_temp_kelvin(self) -> int:
        """Return the CT color value in Kelvin."""
        return cast(int, self.status["ct"])

    @override
    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn on light."""
        params: dict[str, Any] = {"id": self._id, "on": True}

        if ATTR_BRIGHTNESS in kwargs:
            params["brightness"] = brightness_to_percentage(kwargs[ATTR_BRIGHTNESS])

        if ATTR_COLOR_TEMP_KELVIN in kwargs:
            params["ct"] = kwargs[ATTR_COLOR_TEMP_KELVIN]

        if ATTR_TRANSITION in kwargs:
            params["transition_duration"] = max(
                kwargs[ATTR_TRANSITION], RPC_MIN_TRANSITION_TIME_SEC
            )

        if ATTR_RGB_COLOR in kwargs:
            params["rgb"] = list(kwargs[ATTR_RGB_COLOR])

        if ATTR_RGBW_COLOR in kwargs:
            params["rgb"] = list(kwargs[ATTR_RGBW_COLOR][:-1])
            params["white"] = kwargs[ATTR_RGBW_COLOR][-1]

        if self.status.get("mode") is not None:
            if ATTR_COLOR_TEMP_KELVIN in kwargs:
                params["mode"] = "cct"
            elif ATTR_RGB_COLOR in kwargs:
                params["mode"] = "rgb"

        await self.call_rpc(f"{self._component}.Set", params)

    @override
    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn off light."""
        params: dict[str, Any] = {"id": self._id, "on": False}

        if ATTR_TRANSITION in kwargs:
            params["transition_duration"] = max(
                kwargs[ATTR_TRANSITION], RPC_MIN_TRANSITION_TIME_SEC
            )

        await self.call_rpc(f"{self._component}.Set", params)


class RpcShellySwitchAsLight(RpcShellyLightBase):
    """Entity that controls a relay as light on RPC based Shelly devices."""

    _component = "Switch"

    _attr_color_mode = ColorMode.ONOFF
    _attr_supported_color_modes = {ColorMode.ONOFF}


class RpcShellyLight(RpcShellyLightBase):
    """Entity that controls a light on RPC based Shelly devices."""

    _component = "Light"

    _attr_color_mode = ColorMode.BRIGHTNESS
    _attr_supported_color_modes = {ColorMode.BRIGHTNESS}
    _attr_supported_features = LightEntityFeature.TRANSITION


class RpcShellyCctLight(RpcShellyLightBase):
    """Entity that controls a CCT light on RPC based Shelly devices."""

    _component = "CCT"

    _attr_color_mode = ColorMode.COLOR_TEMP
    _attr_supported_color_modes = {ColorMode.COLOR_TEMP}
    _attr_supported_features = LightEntityFeature.TRANSITION

    def __init__(
        self,
        coordinator: ShellyRpcCoordinator,
        key: str,
        attribute: str,
        description: RpcEntityDescription,
    ) -> None:
        """Initialize light."""
        super().__init__(coordinator, key, attribute, description)
        if color_temp_range := coordinator.device.config[key].get("ct_range"):
            self._attr_min_color_temp_kelvin = color_temp_range[0]
            self._attr_max_color_temp_kelvin = color_temp_range[1]
        else:
            self._attr_min_color_temp_kelvin = KELVIN_MIN_VALUE_WHITE
            self._attr_max_color_temp_kelvin = KELVIN_MAX_VALUE


class RpcShellyRgbCctLight(RpcShellyLightBase):
    """Entity that controls a RGBCCT light on RPC based Shelly devices."""

    _component = "RGBCCT"

    _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.RGB}
    _attr_supported_features = LightEntityFeature.TRANSITION
    _attr_min_color_temp_kelvin = KELVIN_MIN_VALUE_WHITE
    _attr_max_color_temp_kelvin = KELVIN_MAX_VALUE

    @property
    @override
    def color_mode(self) -> ColorMode:
        """Return the color mode."""
        if self.status["mode"] == "cct":
            return ColorMode.COLOR_TEMP

        return ColorMode.RGB


class RpcShellyRgbLight(RpcShellyLightBase):
    """Entity that controls a RGB light on RPC based Shelly devices."""

    _component = "RGB"

    _attr_color_mode = ColorMode.RGB
    _attr_supported_color_modes = {ColorMode.RGB}
    _attr_supported_features = LightEntityFeature.TRANSITION


class RpcShellyRgbwLight(RpcShellyLightBase):
    """Entity that controls a RGBW light on RPC based Shelly devices."""

    _component = "RGBW"

    _attr_color_mode = ColorMode.RGBW
    _attr_supported_color_modes = {ColorMode.RGBW}
    _attr_supported_features = LightEntityFeature.TRANSITION


LIGHTS: Final = {
    "switch": RpcEntityDescription(
        key="switch",
        sub_key="output",
        removal_condition=lambda config, _status, key: (
            not is_rpc_channel_type_light(config, get_rpc_key_id(key))
        ),
        entity_class=RpcShellySwitchAsLight,
    ),
    "light": RpcEntityDescription(
        key="light",
        sub_key="output",
        entity_class=RpcShellyLight,
    ),
    "cct": RpcEntityDescription(
        key="cct",
        sub_key="output",
        entity_class=RpcShellyCctLight,
    ),
    "rgb": RpcEntityDescription(
        key="rgb",
        sub_key="output",
        entity_class=RpcShellyRgbLight,
    ),
    "rgbcct": RpcEntityDescription(
        key="rgbcct",
        sub_key="output",
        entity_class=RpcShellyRgbCctLight,
    ),
    "rgbw": RpcEntityDescription(
        key="rgbw",
        sub_key="output",
        entity_class=RpcShellyRgbwLight,
    ),
}


@callback
def _async_setup_rpc_entry(
    hass: HomeAssistant,
    config_entry: ShellyConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up entities for RPC device."""
    coordinator = config_entry.runtime_data.rpc
    assert coordinator

    async_setup_entry_rpc(
        hass, config_entry, async_add_entities, LIGHTS, RpcShellyLight
    )

    async_remove_orphaned_entities(
        hass,
        config_entry.entry_id,
        coordinator.mac,
        LIGHT_DOMAIN,
        coordinator.device.status,
    )
