"""WiZ integration."""
from __future__ import annotations

import contextlib
import logging
from typing import Any

from pywizlight import PilotBuilder
from pywizlight.bulblibrary import BulbClass, BulbType
from pywizlight.exceptions import WizLightNotKnownBulb
from pywizlight.rgbcw import convertHSfromRGBCW
from pywizlight.scenes import get_id_from_scene_name

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_COLOR_TEMP,
    ATTR_EFFECT,
    ATTR_HS_COLOR,
    ATTR_RGB_COLOR,
    COLOR_MODE_BRIGHTNESS,
    COLOR_MODE_COLOR_TEMP,
    COLOR_MODE_HS,
    SUPPORT_EFFECT,
    LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
import homeassistant.util.color as color_utils

from .const import DOMAIN
from .models import WizData

_LOGGER = logging.getLogger(__name__)

DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP}
DEFAULT_MIN_MIREDS = 153
DEFAULT_MAX_MIREDS = 454


def get_supported_color_modes(bulb_type: BulbType) -> set[str]:
    """Flag supported features."""
    if not bulb_type:
        # fallback
        return DEFAULT_COLOR_MODES
    color_modes = set()
    try:
        features = bulb_type.features
        if features.color:
            color_modes.add(COLOR_MODE_HS)
        if features.color_tmp:
            color_modes.add(COLOR_MODE_COLOR_TEMP)
        if not color_modes and features.brightness:
            color_modes.add(COLOR_MODE_BRIGHTNESS)
        return color_modes
    except WizLightNotKnownBulb:
        _LOGGER.warning("Bulb is not present in the library. Fallback to full feature")
        return DEFAULT_COLOR_MODES


def supports_effects(bulb_type: BulbType) -> bool:
    """Check if a bulb supports effects."""
    with contextlib.suppress(WizLightNotKnownBulb):
        return bool(bulb_type.features.effect)
    return True  # default is true


def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]:
    """Return the coldest and warmest color_temp that this light supports."""
    if bulb_type is None:
        return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS
    # DW bulbs have no kelvin
    if bulb_type.bulb_type == BulbClass.DW:
        return 0, 0
    # If bulbtype is TW or RGB then return the kelvin value
    try:
        return color_utils.color_temperature_kelvin_to_mired(
            bulb_type.kelvin_range.max
        ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min)
    except WizLightNotKnownBulb:
        _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500")
        return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the WiZ Platform from config_flow."""
    wiz_data: WizData = hass.data[DOMAIN][entry.entry_id]
    async_add_entities([WizBulbEntity(wiz_data, entry.title)])


class WizBulbEntity(CoordinatorEntity, LightEntity):
    """Representation of WiZ Light bulb."""

    def __init__(self, wiz_data: WizData, name: str) -> None:
        """Initialize an WiZLight."""
        super().__init__(wiz_data.coordinator)
        self._light = wiz_data.bulb
        bulb_type: BulbType = self._light.bulbtype
        self._attr_unique_id = self._light.mac
        self._attr_name = name
        self._attr_effect_list = wiz_data.scenes
        self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type)
        self._attr_supported_color_modes = get_supported_color_modes(bulb_type)
        if supports_effects(bulb_type):
            self._attr_supported_features = SUPPORT_EFFECT
        self._attr_device_info = DeviceInfo(
            connections={(CONNECTION_NETWORK_MAC, self._light.mac)},
            name=name,
            manufacturer="WiZ",
            model=bulb_type.name,
        )

    @property
    def is_on(self) -> bool | None:
        """Return true if light is on."""
        is_on: bool | None = self._light.status
        return is_on

    @property
    def brightness(self) -> int | None:
        """Return the brightness of the light."""
        if (brightness := self._light.state.get_brightness()) is None:
            return None
        if 0 <= int(brightness) <= 255:
            return int(brightness)
        _LOGGER.error("Received invalid brightness : %s. Expected: 0-255", brightness)
        return None

    @property
    def color_mode(self) -> str:
        """Return the current color mode."""
        color_modes = self.supported_color_modes
        assert color_modes is not None
        if (
            COLOR_MODE_COLOR_TEMP in color_modes
            and self._light.state.get_colortemp() is not None
        ):
            return COLOR_MODE_COLOR_TEMP
        if (
            COLOR_MODE_HS in color_modes
            and (rgb := self._light.state.get_rgb()) is not None
            and rgb[0] is not None
        ):
            return COLOR_MODE_HS
        return COLOR_MODE_BRIGHTNESS

    @property
    def hs_color(self) -> tuple[float, float] | None:
        """Return the hs color value."""
        colortemp = self._light.state.get_colortemp()
        if colortemp is not None and colortemp != 0:
            return None
        if (rgb := self._light.state.get_rgb()) is None:
            return None
        if rgb[0] is None:
            # this is the case if the temperature was changed
            # do nothing until the RGB color was changed
            return None
        if (warmwhite := self._light.state.get_warm_white()) is None:
            return None
        hue_sat = convertHSfromRGBCW(rgb, warmwhite)
        hue: float = hue_sat[0]
        sat: float = hue_sat[1]
        return hue, sat

    @property
    def color_temp(self) -> int | None:
        """Return the CT color value in mireds."""
        colortemp = self._light.state.get_colortemp()
        if colortemp is None or colortemp == 0:
            return None
        _LOGGER.debug(
            "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp
        )
        return color_utils.color_temperature_kelvin_to_mired(colortemp)

    @property
    def effect(self) -> str | None:
        """Return the current effect."""
        effect: str | None = self._light.state.get_scene()
        return effect

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Instruct the light to turn on."""
        brightness = None

        if ATTR_BRIGHTNESS in kwargs:
            brightness = kwargs.get(ATTR_BRIGHTNESS)

        if ATTR_RGB_COLOR in kwargs:
            pilot = PilotBuilder(rgb=kwargs.get(ATTR_RGB_COLOR), brightness=brightness)

        if ATTR_HS_COLOR in kwargs:
            pilot = PilotBuilder(
                hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]),
                brightness=brightness,
            )
        else:
            colortemp = None
            if ATTR_COLOR_TEMP in kwargs:
                kelvin = color_utils.color_temperature_mired_to_kelvin(
                    kwargs[ATTR_COLOR_TEMP]
                )
                colortemp = kelvin
                _LOGGER.debug(
                    "[wizlight %s] kelvin changed and send to bulb: %s",
                    self._light.ip,
                    colortemp,
                )

            sceneid = None
            if ATTR_EFFECT in kwargs:
                sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT])

            if sceneid == 1000:  # rhythm
                pilot = PilotBuilder()
            else:
                pilot = PilotBuilder(
                    brightness=brightness, colortemp=colortemp, scene=sceneid
                )
                _LOGGER.debug(
                    "[wizlight %s] Pilot will be send with brightness=%s, colortemp=%s, scene=%s",
                    self._light.ip,
                    brightness,
                    colortemp,
                    sceneid,
                )

            sceneid = None
            if ATTR_EFFECT in kwargs:
                sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT])

            if sceneid == 1000:  # rhythm
                pilot = PilotBuilder()
            else:
                pilot = PilotBuilder(
                    brightness=brightness, colortemp=colortemp, scene=sceneid
                )

        await self._light.turn_on(pilot)
        await self.coordinator.async_request_refresh()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Instruct the light to turn off."""
        await self._light.turn_off()
        await self.coordinator.async_request_refresh()