"""Support for LED lights."""
import logging
from typing import Any, Callable, List, Optional, Tuple

from wled import WLED, Effect, WLEDError

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP,
    ATTR_EFFECT,
    ATTR_HS_COLOR,
    ATTR_TRANSITION,
    ATTR_WHITE_VALUE,
    SUPPORT_BRIGHTNESS,
    SUPPORT_COLOR,
    SUPPORT_COLOR_TEMP,
    SUPPORT_EFFECT,
    SUPPORT_TRANSITION,
    SUPPORT_WHITE_VALUE,
    Light,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType
import homeassistant.util.color as color_util

from . import WLEDDeviceEntity
from .const import (
    ATTR_COLOR_PRIMARY,
    ATTR_INTENSITY,
    ATTR_ON,
    ATTR_PALETTE,
    ATTR_PLAYLIST,
    ATTR_PRESET,
    ATTR_SEGMENT_ID,
    ATTR_SPEED,
    DATA_WLED_CLIENT,
    DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 1


async def async_setup_entry(
    hass: HomeAssistantType,
    entry: ConfigEntry,
    async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
    """Set up WLED light based on a config entry."""
    wled: WLED = hass.data[DOMAIN][entry.entry_id][DATA_WLED_CLIENT]

    # Does the WLED device support RGBW
    rgbw = wled.device.info.leds.rgbw

    # List of supported effects
    effects = wled.device.effects

    # WLED supports splitting a strip in multiple segments
    # Each segment will be a separate light in Home Assistant
    lights = []
    for light in wled.device.state.segments:
        lights.append(WLEDLight(entry.entry_id, wled, light.segment_id, rgbw, effects))

    async_add_entities(lights, True)


class WLEDLight(Light, WLEDDeviceEntity):
    """Defines a WLED light."""

    def __init__(
        self, entry_id: str, wled: WLED, segment: int, rgbw: bool, effects: List[Effect]
    ):
        """Initialize WLED light."""
        self._effects = effects
        self._rgbw = rgbw
        self._segment = segment

        self._brightness: Optional[int] = None
        self._color: Optional[Tuple[float, float]] = None
        self._effect: Optional[str] = None
        self._state: Optional[bool] = None
        self._white_value: Optional[int] = None

        # Only apply the segment ID if it is not the first segment
        name = wled.device.info.name
        if segment != 0:
            name += f" {segment}"

        super().__init__(entry_id, wled, name, "mdi:led-strip-variant")

    @property
    def unique_id(self) -> str:
        """Return the unique ID for this sensor."""
        return f"{self.wled.device.info.mac_address}_{self._segment}"

    @property
    def hs_color(self) -> Optional[Tuple[float, float]]:
        """Return the hue and saturation color value [float, float]."""
        return self._color

    @property
    def effect(self) -> Optional[str]:
        """Return the current effect of the light."""
        return self._effect

    @property
    def brightness(self) -> Optional[int]:
        """Return the brightness of this light between 1..255."""
        return self._brightness

    @property
    def white_value(self) -> Optional[int]:
        """Return the white value of this light between 0..255."""
        return self._white_value

    @property
    def supported_features(self) -> int:
        """Flag supported features."""
        flags = (
            SUPPORT_BRIGHTNESS
            | SUPPORT_COLOR
            | SUPPORT_COLOR_TEMP
            | SUPPORT_EFFECT
            | SUPPORT_TRANSITION
        )

        if self._rgbw:
            flags |= SUPPORT_WHITE_VALUE

        return flags

    @property
    def effect_list(self) -> List[str]:
        """Return the list of supported effects."""
        return [effect.name for effect in self._effects]

    @property
    def is_on(self) -> bool:
        """Return the state of the light."""
        return bool(self._state)

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn off the light."""
        try:
            await self.wled.light(on=False)
            self._state = False
        except WLEDError:
            _LOGGER.error("An error occurred while turning off WLED light.")
            self._available = False
        self.async_schedule_update_ha_state()

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn on the light."""
        data = {ATTR_ON: True, ATTR_SEGMENT_ID: self._segment}

        if ATTR_COLOR_TEMP in kwargs:
            mireds = color_util.color_temperature_kelvin_to_mired(
                kwargs[ATTR_COLOR_TEMP]
            )
            data[ATTR_COLOR_PRIMARY] = tuple(
                map(int, color_util.color_temperature_to_rgb(mireds))
            )

        if ATTR_HS_COLOR in kwargs:
            hue, sat = kwargs[ATTR_HS_COLOR]
            data[ATTR_COLOR_PRIMARY] = color_util.color_hsv_to_RGB(hue, sat, 100)

        if ATTR_TRANSITION in kwargs:
            data[ATTR_TRANSITION] = kwargs[ATTR_TRANSITION]

        if ATTR_BRIGHTNESS in kwargs:
            data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]

        if ATTR_EFFECT in kwargs:
            data[ATTR_EFFECT] = kwargs[ATTR_EFFECT]

        # Support for RGBW strips, adds white value
        if self._rgbw and any(
            x in (ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_WHITE_VALUE) for x in kwargs
        ):
            # WLED cannot just accept a white value, it needs the color.
            # We use the last know color in case just the white value changes.
            if not any(x in (ATTR_COLOR_TEMP, ATTR_HS_COLOR) for x in kwargs):
                hue, sat = self._color
                data[ATTR_COLOR_PRIMARY] = color_util.color_hsv_to_RGB(hue, sat, 100)

            # Add requested or last known white value
            if ATTR_WHITE_VALUE in kwargs:
                data[ATTR_COLOR_PRIMARY] += (kwargs[ATTR_WHITE_VALUE],)
            else:
                data[ATTR_COLOR_PRIMARY] += (self._white_value,)

        try:
            await self.wled.light(**data)

            self._state = True

            if ATTR_BRIGHTNESS in kwargs:
                self._brightness = kwargs[ATTR_BRIGHTNESS]

            if ATTR_EFFECT in kwargs:
                self._effect = kwargs[ATTR_EFFECT]

            if ATTR_HS_COLOR in kwargs:
                self._color = kwargs[ATTR_HS_COLOR]

            if ATTR_COLOR_TEMP in kwargs:
                self._color = color_util.color_temperature_to_hs(mireds)

            if ATTR_WHITE_VALUE in kwargs:
                self._white_value = kwargs[ATTR_WHITE_VALUE]

        except WLEDError:
            _LOGGER.error("An error occurred while turning on WLED light.")
            self._available = False
        self.async_schedule_update_ha_state()

    async def _wled_update(self) -> None:
        """Update WLED entity."""
        self._brightness = self.wled.device.state.brightness
        self._effect = self.wled.device.state.segments[self._segment].effect.name
        self._state = self.wled.device.state.on

        color = self.wled.device.state.segments[self._segment].color_primary
        self._color = color_util.color_RGB_to_hs(*color[:3])
        if self._rgbw:
            self._white_value = color[-1]

        playlist = self.wled.device.state.playlist
        if playlist == -1:
            playlist = None

        preset = self.wled.device.state.preset
        if preset == -1:
            preset = None

        self._attributes = {
            ATTR_INTENSITY: self.wled.device.state.segments[self._segment].intensity,
            ATTR_PALETTE: self.wled.device.state.segments[self._segment].palette.name,
            ATTR_PLAYLIST: playlist,
            ATTR_PRESET: preset,
            ATTR_SPEED: self.wled.device.state.segments[self._segment].speed,
        }
