"""Matter sensors."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from functools import partial
from typing import Any

from chip.clusters import Objects as clusters
from chip.clusters.Types import Nullable, NullValue
from matter_server.common.models import device_types
from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    LIGHT_LUX,
    PERCENTAGE,
    Platform,
    UnitOfPressure,
    UnitOfTemperature,
    UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .entity import MatterEntity, MatterEntityDescriptionBaseClass
from .helpers import get_matter


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Matter sensors from Config Entry."""
    matter = get_matter(hass)
    matter.register_platform_handler(Platform.SENSOR, async_add_entities)


class MatterSensor(MatterEntity, SensorEntity):
    """Representation of a Matter sensor."""

    _attr_state_class = SensorStateClass.MEASUREMENT
    entity_description: MatterSensorEntityDescription

    @callback
    def _update_from_device(self) -> None:
        """Update from device."""
        measurement: Nullable | float | None
        measurement = _get_attribute_value(
            self._device_type_instance,
            # We always subscribe to a single value
            self.entity_description.subscribe_attributes[0],
        )

        if measurement is NullValue or measurement is None:
            measurement = None
        else:
            measurement = self.entity_description.measurement_to_ha(measurement)

        self._attr_native_value = measurement


def _get_attribute_value(
    device_type_instance: MatterDeviceTypeInstance,
    attribute: clusters.ClusterAttributeDescriptor,
) -> Any:
    """Return the value of an attribute."""
    # Find the cluster for this attribute. We don't have a lookup table yet.
    cluster_cls: clusters.Cluster = next(
        cluster
        for cluster in device_type_instance.device_type.clusters
        if cluster.id == attribute.cluster_id
    )

    # Find the attribute descriptor so we know the instance variable to fetch
    attribute_descriptor: clusters.ClusterObjectFieldDescriptor = next(
        descriptor
        for descriptor in cluster_cls.descriptor.Fields
        if descriptor.Tag == attribute.attribute_id
    )

    cluster_data = device_type_instance.get_cluster(cluster_cls)
    return getattr(cluster_data, attribute_descriptor.Label)


@dataclass
class MatterSensorEntityDescriptionMixin:
    """Required fields for sensor device mapping."""

    measurement_to_ha: Callable[[float], float]


@dataclass
class MatterSensorEntityDescription(
    SensorEntityDescription,
    MatterEntityDescriptionBaseClass,
    MatterSensorEntityDescriptionMixin,
):
    """Matter Sensor entity description."""


# You can't set default values on inherited data classes
MatterSensorEntityDescriptionFactory = partial(
    MatterSensorEntityDescription, entity_cls=MatterSensor
)


DEVICE_ENTITY: dict[
    type[device_types.DeviceType],
    MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
] = {
    device_types.TemperatureSensor: MatterSensorEntityDescriptionFactory(
        key=device_types.TemperatureSensor,
        name="Temperature",
        measurement_to_ha=lambda x: x / 100,
        subscribe_attributes=(
            clusters.TemperatureMeasurement.Attributes.MeasuredValue,
        ),
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
    ),
    device_types.PressureSensor: MatterSensorEntityDescriptionFactory(
        key=device_types.PressureSensor,
        name="Pressure",
        measurement_to_ha=lambda x: x / 10,
        subscribe_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,),
        native_unit_of_measurement=UnitOfPressure.KPA,
        device_class=SensorDeviceClass.PRESSURE,
    ),
    device_types.FlowSensor: MatterSensorEntityDescriptionFactory(
        key=device_types.FlowSensor,
        name="Flow",
        measurement_to_ha=lambda x: x / 10,
        subscribe_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,),
        native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
    ),
    device_types.HumiditySensor: MatterSensorEntityDescriptionFactory(
        key=device_types.HumiditySensor,
        name="Humidity",
        measurement_to_ha=lambda x: x / 100,
        subscribe_attributes=(
            clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue,
        ),
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
    ),
    device_types.LightSensor: MatterSensorEntityDescriptionFactory(
        key=device_types.LightSensor,
        name="Light",
        measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1),
        subscribe_attributes=(
            clusters.IlluminanceMeasurement.Attributes.MeasuredValue,
        ),
        native_unit_of_measurement=LIGHT_LUX,
        device_class=SensorDeviceClass.ILLUMINANCE,
    ),
}
