"""Provides number entities for Home Connect."""

import logging
from typing import cast

from aiohomeconnect.model import GetSetting, OptionKey, SettingKey
from aiohomeconnect.model.error import HomeConnectError

from homeassistant.components.number import (
    NumberDeviceClass,
    NumberEntity,
    NumberEntityDescription,
)
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .common import setup_home_connect_entry
from .const import DOMAIN, UNIT_MAP
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity, HomeConnectOptionEntity, constraint_fetcher
from .utils import get_dict_from_home_connect_error

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 1

NUMBERS = (
    NumberEntityDescription(
        key=SettingKey.BSH_COMMON_ALARM_CLOCK,
        device_class=NumberDeviceClass.DURATION,
        translation_key="alarm_clock",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_REFRIGERATOR,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="refrigerator_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_FREEZER,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="freezer_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_BOTTLE_COOLER_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="bottle_cooler_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_CHILLER_LEFT_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="chiller_left_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_CHILLER_COMMON_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="chiller_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_CHILLER_RIGHT_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="chiller_right_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_WINE_COMPARTMENT_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="wine_compartment_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_WINE_COMPARTMENT_2_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="wine_compartment_2_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.REFRIGERATION_COMMON_WINE_COMPARTMENT_3_SETPOINT_TEMPERATURE,
        device_class=NumberDeviceClass.TEMPERATURE,
        translation_key="wine_compartment_3_setpoint_temperature",
    ),
    NumberEntityDescription(
        key=SettingKey.COOKING_HOOD_COLOR_TEMPERATURE_PERCENT,
        translation_key="color_temperature_percent",
        native_unit_of_measurement=PERCENTAGE,
    ),
    NumberEntityDescription(
        key=SettingKey.LAUNDRY_CARE_WASHER_I_DOS_1_BASE_LEVEL,
        device_class=NumberDeviceClass.VOLUME,
        translation_key="washer_i_dos_1_base_level",
    ),
    NumberEntityDescription(
        key=SettingKey.LAUNDRY_CARE_WASHER_I_DOS_2_BASE_LEVEL,
        device_class=NumberDeviceClass.VOLUME,
        translation_key="washer_i_dos_2_base_level",
    ),
)

NUMBER_OPTIONS = (
    NumberEntityDescription(
        key=OptionKey.BSH_COMMON_DURATION,
        translation_key="duration",
    ),
    NumberEntityDescription(
        key=OptionKey.BSH_COMMON_FINISH_IN_RELATIVE,
        translation_key="finish_in_relative",
    ),
    NumberEntityDescription(
        key=OptionKey.BSH_COMMON_START_IN_RELATIVE,
        translation_key="start_in_relative",
    ),
    NumberEntityDescription(
        key=OptionKey.CONSUMER_PRODUCTS_COFFEE_MAKER_FILL_QUANTITY,
        translation_key="fill_quantity",
        device_class=NumberDeviceClass.VOLUME,
        native_step=1,
    ),
    NumberEntityDescription(
        key=OptionKey.COOKING_OVEN_SETPOINT_TEMPERATURE,
        translation_key="setpoint_temperature",
        device_class=NumberDeviceClass.TEMPERATURE,
    ),
)


def _get_entities_for_appliance(
    entry: HomeConnectConfigEntry,
    appliance: HomeConnectApplianceData,
) -> list[HomeConnectEntity]:
    """Get a list of entities."""
    return [
        HomeConnectNumberEntity(entry.runtime_data, appliance, description)
        for description in NUMBERS
        if description.key in appliance.settings
    ]


def _get_option_entities_for_appliance(
    entry: HomeConnectConfigEntry,
    appliance: HomeConnectApplianceData,
) -> list[HomeConnectOptionEntity]:
    """Get a list of currently available option entities."""
    return [
        HomeConnectOptionNumberEntity(entry.runtime_data, appliance, description)
        for description in NUMBER_OPTIONS
        if description.key in appliance.options
    ]


async def async_setup_entry(
    hass: HomeAssistant,
    entry: HomeConnectConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Home Connect number."""
    setup_home_connect_entry(
        entry,
        _get_entities_for_appliance,
        async_add_entities,
        _get_option_entities_for_appliance,
    )


class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
    """Number setting class for Home Connect."""

    async def async_set_native_value(self, value: float) -> None:
        """Set the native value of the entity."""
        _LOGGER.debug(
            "Tried to set value %s to %s for %s",
            value,
            self.bsh_key,
            self.entity_id,
        )
        try:
            await self.coordinator.client.set_setting(
                self.appliance.info.ha_id,
                setting_key=SettingKey(self.bsh_key),
                value=value,
            )
        except HomeConnectError as err:
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="set_setting_entity",
                translation_placeholders={
                    **get_dict_from_home_connect_error(err),
                    "entity_id": self.entity_id,
                    "key": self.bsh_key,
                    "value": str(value),
                },
            ) from err

    @constraint_fetcher
    async def async_fetch_constraints(self) -> None:
        """Fetch the max and min values and step for the number entity."""
        setting_key = cast(SettingKey, self.bsh_key)
        data = self.appliance.settings.get(setting_key)
        if not data or not data.unit or not data.constraints:
            data = await self.coordinator.client.get_setting(
                self.appliance.info.ha_id, setting_key=setting_key
            )
            if data.unit:
                self._attr_native_unit_of_measurement = data.unit
            self.set_constraints(data)

    def set_constraints(self, setting: GetSetting) -> None:
        """Set constraints for the number entity."""
        if setting.unit:
            self._attr_native_unit_of_measurement = UNIT_MAP.get(
                setting.unit, setting.unit
            )
        if not (constraints := setting.constraints):
            return
        if constraints.max:
            self._attr_native_max_value = constraints.max
        if constraints.min:
            self._attr_native_min_value = constraints.min
        if constraints.step_size:
            self._attr_native_step = constraints.step_size
        else:
            self._attr_native_step = 0.1 if setting.type == "Double" else 1

    def update_native_value(self) -> None:
        """Update status when an event for the entity is received."""
        data = self.appliance.settings[cast(SettingKey, self.bsh_key)]
        self._attr_native_value = cast(float, data.value)

    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        data = self.appliance.settings[cast(SettingKey, self.bsh_key)]
        self.set_constraints(data)
        if (
            not hasattr(self, "_attr_native_unit_of_measurement")
            or not hasattr(self, "_attr_native_min_value")
            or not hasattr(self, "_attr_native_max_value")
            or not hasattr(self, "_attr_native_step")
        ):
            await self.async_fetch_constraints()


class HomeConnectOptionNumberEntity(HomeConnectOptionEntity, NumberEntity):
    """Number option class for Home Connect."""

    async def async_set_native_value(self, value: float) -> None:
        """Set the native value of the entity."""
        await self.async_set_option(value)

    def update_native_value(self) -> None:
        """Set the value of the entity."""
        self._attr_native_value = cast(float | None, self.option_value)
        option_definition = self.appliance.options.get(self.bsh_key)
        if option_definition:
            if option_definition.unit:
                candidate_unit = UNIT_MAP.get(
                    option_definition.unit, option_definition.unit
                )
                if (
                    not hasattr(self, "_attr_native_unit_of_measurement")
                    or candidate_unit != self._attr_native_unit_of_measurement
                ):
                    self._attr_native_unit_of_measurement = candidate_unit
            option_constraints = option_definition.constraints
            if option_constraints:
                if (
                    not hasattr(self, "_attr_native_min_value")
                    or self._attr_native_min_value != option_constraints.min
                ) and option_constraints.min:
                    self._attr_native_min_value = option_constraints.min
                if (
                    not hasattr(self, "_attr_native_max_value")
                    or self._attr_native_max_value != option_constraints.max
                ) and option_constraints.max:
                    self._attr_native_max_value = option_constraints.max
                if (
                    not hasattr(self, "_attr_native_step")
                    or self._attr_native_step != option_constraints.step_size
                ) and option_constraints.step_size:
                    self._attr_native_step = option_constraints.step_size
