"""Support for configuring different deCONZ numbers."""

from __future__ import annotations

from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any, Generic, TypeVar

from pydeconz.gateway import DeconzSession
from pydeconz.interfaces.sensors import SensorResources
from pydeconz.models.event import EventType
from pydeconz.models.sensor import SensorBase as PydeconzSensorBase
from pydeconz.models.sensor.presence import Presence

from homeassistant.components.number import (
    DOMAIN,
    NumberEntity,
    NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.helpers.entity_registry as er

from .const import DOMAIN as DECONZ_DOMAIN
from .deconz_device import DeconzDevice
from .gateway import DeconzGateway, get_gateway_from_config_entry
from .util import serial_from_unique_id

T = TypeVar("T", Presence, PydeconzSensorBase)


@dataclass
class DeconzNumberDescriptionMixin(Generic[T]):
    """Required values when describing deCONZ number entities."""

    instance_check: type[T]
    name_suffix: str
    set_fn: Callable[[DeconzSession, str, int], Coroutine[Any, Any, dict[str, Any]]]
    update_key: str
    value_fn: Callable[[T], float | None]


@dataclass
class DeconzNumberDescription(NumberEntityDescription, DeconzNumberDescriptionMixin[T]):
    """Class describing deCONZ number entities."""


ENTITY_DESCRIPTIONS: tuple[DeconzNumberDescription, ...] = (
    DeconzNumberDescription[Presence](
        key="delay",
        instance_check=Presence,
        name_suffix="Delay",
        set_fn=lambda api, id, v: api.sensors.presence.set_config(id=id, delay=v),
        update_key="delay",
        value_fn=lambda device: device.delay,
        native_max_value=65535,
        native_min_value=0,
        native_step=1,
        entity_category=EntityCategory.CONFIG,
    ),
    DeconzNumberDescription[Presence](
        key="duration",
        instance_check=Presence,
        name_suffix="Duration",
        set_fn=lambda api, id, v: api.sensors.presence.set_config(id=id, duration=v),
        update_key="duration",
        value_fn=lambda device: device.duration,
        native_max_value=65535,
        native_min_value=0,
        native_step=1,
        entity_category=EntityCategory.CONFIG,
    ),
)


@callback
def async_update_unique_id(
    hass: HomeAssistant, unique_id: str, description: DeconzNumberDescription
) -> None:
    """Update unique ID base to be on full unique ID rather than device serial.

    Introduced with release 2022.11.
    """
    ent_reg = er.async_get(hass)

    new_unique_id = f"{unique_id}-{description.key}"
    if ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, new_unique_id):
        return

    unique_id = f"{serial_from_unique_id(unique_id)}-{description.key}"
    if entity_id := ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, unique_id):
        ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the deCONZ number entity."""
    gateway = get_gateway_from_config_entry(hass, config_entry)
    gateway.entities[DOMAIN] = set()

    @callback
    def async_add_sensor(_: EventType, sensor_id: str) -> None:
        """Add sensor from deCONZ."""
        sensor = gateway.api.sensors.presence[sensor_id]

        for description in ENTITY_DESCRIPTIONS:
            if (
                not isinstance(sensor, description.instance_check)
                or description.value_fn(sensor) is None
            ):
                continue
            if description.key == "delay":
                async_update_unique_id(hass, sensor.unique_id, description)
            async_add_entities([DeconzNumber(sensor, gateway, description)])

    gateway.register_platform_add_device_callback(
        async_add_sensor,
        gateway.api.sensors.presence,
        always_ignore_clip_sensors=True,
    )


class DeconzNumber(DeconzDevice[SensorResources], NumberEntity):
    """Representation of a deCONZ number entity."""

    TYPE = DOMAIN
    entity_description: DeconzNumberDescription

    def __init__(
        self,
        device: SensorResources,
        gateway: DeconzGateway,
        description: DeconzNumberDescription,
    ) -> None:
        """Initialize deCONZ number entity."""
        self.entity_description = description
        self.unique_id_suffix = description.key
        self._name_suffix = description.name_suffix
        self._update_key = description.update_key
        super().__init__(device, gateway)

    @property
    def native_value(self) -> float | None:
        """Return the value of the sensor property."""
        return self.entity_description.value_fn(self._device)

    async def async_set_native_value(self, value: float) -> None:
        """Set sensor config."""
        await self.entity_description.set_fn(
            self.gateway.api,
            self._device.resource_id,
            int(value),
        )
