"""Light preset module."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import asdict
from typing import TYPE_CHECKING, Optional

from pydantic.v1 import BaseModel, Field

from ...exceptions import KasaException
from ...interfaces import LightPreset as LightPresetInterface
from ...interfaces import LightState
from ...module import Module
from ..iotmodule import IotModule

if TYPE_CHECKING:
    pass


class IotLightPreset(BaseModel, LightState):
    """Light configuration preset."""

    index: int = Field(kw_only=True)
    brightness: int = Field(kw_only=True)

    # These are not available for effect mode presets on light strips
    hue: Optional[int] = Field(kw_only=True, default=None)  # noqa: UP007
    saturation: Optional[int] = Field(kw_only=True, default=None)  # noqa: UP007
    color_temp: Optional[int] = Field(kw_only=True, default=None)  # noqa: UP007

    # Variables for effect mode presets
    custom: Optional[int] = Field(kw_only=True, default=None)  # noqa: UP007
    id: Optional[str] = Field(kw_only=True, default=None)  # noqa: UP007
    mode: Optional[int] = Field(kw_only=True, default=None)  # noqa: UP007


class LightPreset(IotModule, LightPresetInterface):
    """Class for setting light presets."""

    _presets: dict[str, IotLightPreset]
    _preset_list: list[str]

    def _post_update_hook(self):
        """Update the internal presets."""
        self._presets = {
            f"Light preset {index+1}": IotLightPreset(**vals)
            for index, vals in enumerate(self.data["preferred_state"])
            # Devices may list some light effects along with normal presets but these
            # are handled by the LightEffect module so exclude preferred states with id
            if "id" not in vals
        }
        self._preset_list = [self.PRESET_NOT_SET]
        self._preset_list.extend(self._presets.keys())

    @property
    def preset_list(self) -> list[str]:
        """Return built-in effects list.

        Example:
            ['Off', 'Preset 1', 'Preset 2', ...]
        """
        return self._preset_list

    @property
    def preset_states_list(self) -> Sequence[IotLightPreset]:
        """Return built-in effects list.

        Example:
            ['Off', 'Preset 1', 'Preset 2', ...]
        """
        return list(self._presets.values())

    @property
    def preset(self) -> str:
        """Return current preset name."""
        light = self._device.modules[Module.Light]
        brightness = light.brightness
        color_temp = light.color_temp if light.is_variable_color_temp else None
        h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None)
        for preset_name, preset in self._presets.items():
            if (
                preset.brightness == brightness
                and (
                    preset.color_temp == color_temp or not light.is_variable_color_temp
                )
                and (preset.hue == h or not light.is_color)
                and (preset.saturation == s or not light.is_color)
            ):
                return preset_name
        return self.PRESET_NOT_SET

    async def set_preset(
        self,
        preset_name: str,
    ) -> None:
        """Set a light preset for the device."""
        light = self._device.modules[Module.Light]
        if preset_name == self.PRESET_NOT_SET:
            if light.is_color:
                preset = LightState(hue=0, saturation=0, brightness=100)
            else:
                preset = LightState(brightness=100)
        elif (preset := self._presets.get(preset_name)) is None:  # type: ignore[assignment]
            raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}")

        await light.set_state(preset)

    @property
    def has_save_preset(self) -> bool:
        """Return True if the device supports updating presets."""
        return True

    async def save_preset(
        self,
        preset_name: str,
        preset_state: LightState,
    ) -> None:
        """Update the preset with preset_name with the new preset_info."""
        if len(self._presets) == 0:
            raise KasaException("Device does not supported saving presets")
        if preset_name not in self._presets:
            raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}")

        index = list(self._presets.keys()).index(preset_name)
        state = asdict(preset_state)
        state = {k: v for k, v in state.items() if v is not None}
        state["index"] = index

        return await self.call("set_preferred_state", state)

    def query(self):
        """Return the base query."""
        return {}

    @property  # type: ignore
    def _deprecated_presets(self) -> list[IotLightPreset]:
        """Return a list of available bulb setting presets."""
        return [
            IotLightPreset(**vals)
            for vals in self._device.sys_info["preferred_state"]
            if "id" not in vals
        ]

    async def _deprecated_save_preset(self, preset: IotLightPreset):
        """Save a setting preset.

        You can either construct a preset object manually, or pass an existing one
        obtained using :func:`presets`.
        """
        if len(self._presets) == 0:
            raise KasaException("Device does not supported saving presets")

        if preset.index >= len(self._presets):
            raise KasaException("Invalid preset index")

        return await self.call("set_preferred_state", preset.dict(exclude_none=True))