"""Component providing sensors for UniFi Protect."""

from __future__ import annotations

from collections.abc import Callable, Sequence
from dataclasses import dataclass
from datetime import datetime
from functools import partial
import logging
from typing import Any

from uiprotect.data import (
    NVR,
    Camera,
    Light,
    ModelType,
    ProtectAdoptableDeviceModel,
    ProtectDeviceModel,
    Sensor,
)

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import (
    LIGHT_LUX,
    PERCENTAGE,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
    EntityCategory,
    UnitOfDataRate,
    UnitOfElectricPotential,
    UnitOfInformation,
    UnitOfTemperature,
    UnitOfTime,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import (
    BaseProtectEntity,
    EventEntityMixin,
    PermRequired,
    ProtectDeviceEntity,
    ProtectEntityDescription,
    ProtectEventMixin,
    ProtectNVREntity,
    T,
    async_all_device_entities,
)
from .utils import async_get_light_motion_current

_LOGGER = logging.getLogger(__name__)
OBJECT_TYPE_NONE = "none"
PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class ProtectSensorEntityDescription(
    ProtectEntityDescription[T], SensorEntityDescription
):
    """Describes UniFi Protect Sensor entity."""

    precision: int | None = None

    def __post_init__(self) -> None:
        """Ensure values are rounded if precision is set."""
        super().__post_init__()
        if precision := self.precision:
            object.__setattr__(
                self,
                "get_ufp_value",
                partial(self._rounded_value, precision, self.get_ufp_value),
            )

    def _rounded_value(self, precision: int, getter: Callable[[T], Any], obj: T) -> Any:
        """Round value to precision if set."""
        return None if (v := getter(obj)) is None else round(v, precision)


@dataclass(frozen=True, kw_only=True)
class ProtectSensorEventEntityDescription(
    ProtectEventMixin[T], SensorEntityDescription
):
    """Describes UniFi Protect Sensor entity."""


def _get_uptime(obj: ProtectDeviceModel) -> datetime | None:
    if obj.up_since is None:
        return None

    # up_since can vary slightly over time
    # truncate to ensure no extra state_change events fire
    return obj.up_since.replace(second=0, microsecond=0)


def _get_nvr_recording_capacity(obj: NVR) -> int:
    if obj.storage_stats.capacity is None:
        return 0

    return int(obj.storage_stats.capacity.total_seconds())


def _get_nvr_memory(obj: NVR) -> float | None:
    memory = obj.system_info.memory
    if memory.available is None or memory.total is None:
        return None
    return (1 - memory.available / memory.total) * 100


def _get_alarm_sound(obj: Sensor) -> str:
    alarm_type = OBJECT_TYPE_NONE
    if (
        obj.is_alarm_detected
        and obj.last_alarm_event is not None
        and obj.last_alarm_event.metadata is not None
    ):
        alarm_type = obj.last_alarm_event.metadata.alarm_type or OBJECT_TYPE_NONE
    return alarm_type.lower()


ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="uptime",
        translation_key="uptime",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
        ufp_value_fn=_get_uptime,
    ),
    ProtectSensorEntityDescription(
        key="ble_signal",
        translation_key="bluetooth_signal_strength",
        native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="bluetooth_connection_state.signal_strength",
        ufp_required_field="bluetooth_connection_state.signal_strength",
    ),
    ProtectSensorEntityDescription(
        key="phy_rate",
        translation_key="link_speed",
        device_class=SensorDeviceClass.DATA_RATE,
        native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="wired_connection_state.phy_rate",
        ufp_required_field="wired_connection_state.phy_rate",
    ),
    ProtectSensorEntityDescription(
        key="wifi_signal",
        translation_key="wifi_signal_strength",
        native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="wifi_connection_state.signal_strength",
        ufp_required_field="wifi_connection_state.signal_strength",
    ),
)

CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="oldest_recording",
        translation_key="oldest_recording",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
        ufp_value="stats.video.recording_start",
    ),
    ProtectSensorEntityDescription(
        key="storage_used",
        translation_key="storage_used",
        native_unit_of_measurement=UnitOfInformation.BYTES,
        device_class=SensorDeviceClass.DATA_SIZE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="stats.storage.used",
        suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
        suggested_display_precision=2,
    ),
    ProtectSensorEntityDescription(
        key="write_rate",
        translation_key="disk_write_rate",
        device_class=SensorDeviceClass.DATA_RATE,
        native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="stats.storage.rate_per_second",
        precision=2,
        suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
        suggested_display_precision=2,
    ),
    ProtectSensorEntityDescription(
        key="voltage",
        device_class=SensorDeviceClass.VOLTAGE,
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="voltage",
        # no feature flag, but voltage will be null if device does not have
        # voltage sensor (i.e. is not G4 Doorbell or not on 1.20.1+)
        ufp_required_field="voltage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="doorbell_last_trip_time",
        translation_key="last_doorbell_ring",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_required_field="feature_flags.is_doorbell",
        ufp_value="last_ring",
        entity_registry_enabled_default=False,
    ),
    ProtectSensorEntityDescription(
        key="lens_type",
        translation_key="lens_type",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_required_field="has_removable_lens",
        ufp_value="feature_flags.lens_type",
    ),
    ProtectSensorEntityDescription(
        key="mic_level",
        translation_key="microphone_level",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_required_field="has_mic",
        ufp_value="mic_volume",
        ufp_enabled="feature_flags.has_mic",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="recording_mode",
        translation_key="recording_mode",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="recording_settings.mode.value",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="infrared",
        translation_key="infrared_mode",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_required_field="feature_flags.has_led_ir",
        ufp_value="isp_settings.ir_led_mode.value",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="doorbell_text",
        translation_key="doorbell_text",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_required_field="feature_flags.has_lcd_screen",
        ufp_value="lcd_message.text",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="chime_type",
        translation_key="chime_type",
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
        ufp_required_field="feature_flags.has_chime",
        ufp_value="chime_type",
    ),
)

CAMERA_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="stats_rx",
        translation_key="received_data",
        native_unit_of_measurement=UnitOfInformation.BYTES,
        device_class=SensorDeviceClass.DATA_SIZE,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.TOTAL_INCREASING,
        ufp_value="stats.rx_bytes",
        suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
        suggested_display_precision=2,
    ),
    ProtectSensorEntityDescription(
        key="stats_tx",
        translation_key="transferred_data",
        native_unit_of_measurement=UnitOfInformation.BYTES,
        device_class=SensorDeviceClass.DATA_SIZE,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.TOTAL_INCREASING,
        ufp_value="stats.tx_bytes",
        suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
        suggested_display_precision=2,
    ),
)

SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="battery_level",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="battery_status.percentage",
    ),
    ProtectSensorEntityDescription(
        key="light_level",
        native_unit_of_measurement=LIGHT_LUX,
        device_class=SensorDeviceClass.ILLUMINANCE,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="stats.light.value",
        ufp_enabled="is_light_sensor_enabled",
    ),
    ProtectSensorEntityDescription(
        key="humidity_level",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="stats.humidity.value",
        ufp_enabled="is_humidity_sensor_enabled",
    ),
    ProtectSensorEntityDescription(
        key="temperature_level",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="stats.temperature.value",
        ufp_enabled="is_temperature_sensor_enabled",
    ),
    ProtectSensorEntityDescription[Sensor](
        key="alarm_sound",
        translation_key="alarm_sound_detected",
        ufp_value_fn=_get_alarm_sound,
        ufp_enabled="is_alarm_sensor_enabled",
    ),
    ProtectSensorEntityDescription(
        key="door_last_trip_time",
        translation_key="last_open",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="open_status_changed_at",
        entity_registry_enabled_default=False,
    ),
    ProtectSensorEntityDescription(
        key="motion_last_trip_time",
        translation_key="last_motion_detected",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="motion_detected_at",
        entity_registry_enabled_default=False,
    ),
    ProtectSensorEntityDescription(
        key="tampering_last_trip_time",
        translation_key="last_tampering_detected",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="tampering_detected_at",
        entity_registry_enabled_default=False,
    ),
    ProtectSensorEntityDescription(
        key="sensitivity",
        translation_key="sensitivity",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="motion_settings.sensitivity",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="mount_type",
        translation_key="mount_type",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="mount_type",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="paired_camera",
        translation_key="paired_camera",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="camera.display_name",
        ufp_perm=PermRequired.NO_WRITE,
    ),
)

DOORLOCK_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="battery_level",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="battery_status.percentage",
    ),
    ProtectSensorEntityDescription(
        key="paired_camera",
        translation_key="paired_camera",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="camera.display_name",
        ufp_perm=PermRequired.NO_WRITE,
    ),
)

NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="uptime",
        translation_key="uptime",
        device_class=SensorDeviceClass.TIMESTAMP,
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value_fn=_get_uptime,
    ),
    ProtectSensorEntityDescription(
        key="storage_utilization",
        translation_key="storage_utilization",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.utilization",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="record_rotating",
        translation_key="type_timelapse_video",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="record_timelapse",
        translation_key="type_continuous_video",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="record_detections",
        translation_key="type_detections_video",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="resolution_HD",
        translation_key="resolution_hd_video",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="resolution_4K",
        translation_key="resolution_4k_video",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription(
        key="resolution_free",
        translation_key="resolution_free_space",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="storage_stats.storage_distribution.free.percentage",
        precision=2,
    ),
    ProtectSensorEntityDescription[NVR](
        key="record_capacity",
        translation_key="recording_capacity",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value_fn=_get_nvr_recording_capacity,
    ),
)

NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="cpu_utilization",
        translation_key="cpu_utilization",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="system_info.cpu.average_load",
    ),
    ProtectSensorEntityDescription(
        key="cpu_temperature",
        translation_key="cpu_temperature",
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        device_class=SensorDeviceClass.TEMPERATURE,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value="system_info.cpu.temperature",
    ),
    ProtectSensorEntityDescription[NVR](
        key="memory_utilization",
        translation_key="memory_utilization",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        state_class=SensorStateClass.MEASUREMENT,
        ufp_value_fn=_get_nvr_memory,
        precision=2,
    ),
)

LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="motion_last_trip_time",
        translation_key="last_motion_detected",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="last_motion",
        entity_registry_enabled_default=False,
    ),
    ProtectSensorEntityDescription(
        key="sensitivity",
        translation_key="motion_sensitivity",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="light_device_settings.pir_sensitivity",
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription[Light](
        key="light_motion",
        translation_key="light_mode",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value_fn=async_get_light_motion_current,
        ufp_perm=PermRequired.NO_WRITE,
    ),
    ProtectSensorEntityDescription(
        key="paired_camera",
        translation_key="paired_camera",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="camera.display_name",
        ufp_perm=PermRequired.NO_WRITE,
    ),
)

MOTION_TRIP_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="motion_last_trip_time",
        translation_key="last_motion_detected",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="last_motion",
        entity_registry_enabled_default=False,
    ),
)

CHIME_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="last_ring",
        translation_key="last_ring",
        device_class=SensorDeviceClass.TIMESTAMP,
        ufp_value="last_ring",
    ),
    ProtectSensorEntityDescription(
        key="volume",
        translation_key="volume",
        native_unit_of_measurement=PERCENTAGE,
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="volume",
        ufp_perm=PermRequired.NO_WRITE,
    ),
)

VIEWER_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
    ProtectSensorEntityDescription(
        key="viewer",
        translation_key="liveview",
        entity_category=EntityCategory.DIAGNOSTIC,
        ufp_value="liveview.name",
        ufp_perm=PermRequired.NO_WRITE,
    ),
)

_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
    ModelType.CAMERA: CAMERA_SENSORS + CAMERA_DISABLED_SENSORS,
    ModelType.SENSOR: SENSE_SENSORS,
    ModelType.LIGHT: LIGHT_SENSORS,
    ModelType.DOORLOCK: DOORLOCK_SENSORS,
    ModelType.CHIME: CHIME_SENSORS,
    ModelType.VIEWPORT: VIEWER_SENSORS,
}


async def async_setup_entry(
    hass: HomeAssistant,
    entry: UFPConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up sensors for UniFi Protect integration."""
    data = entry.runtime_data

    @callback
    def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
        entities = async_all_device_entities(
            data,
            ProtectDeviceSensor,
            all_descs=ALL_DEVICES_SENSORS,
            model_descriptions=_MODEL_DESCRIPTIONS,
            ufp_device=device,
        )
        # AiPort inherits from Camera but should not create camera-specific entities
        if (
            device.model is ModelType.CAMERA
            and isinstance(device, Camera)
            and device.is_adopted_by_us
        ):
            entities += _async_event_entities(data, ufp_device=device)
        async_add_entities(entities)

    data.async_subscribe_adopt(_add_new_device)
    entities = async_all_device_entities(
        data,
        ProtectDeviceSensor,
        all_descs=ALL_DEVICES_SENSORS,
        model_descriptions=_MODEL_DESCRIPTIONS,
    )
    entities += _async_event_entities(data)
    entities += _async_nvr_entities(data)

    async_add_entities(entities)


@callback
def _async_event_entities(
    data: ProtectData,
    ufp_device: Camera | None = None,
) -> list[ProtectDeviceEntity]:
    entities: list[ProtectDeviceEntity] = []
    cameras = data.get_cameras() if ufp_device is None else [ufp_device]
    for camera in cameras:
        for description in MOTION_TRIP_SENSORS:
            entities.append(ProtectDeviceSensor(data, camera, description))
            _LOGGER.debug(
                "Adding trip sensor entity %s for %s",
                description.name,
                camera.display_name,
            )

    return entities


@callback
def _async_nvr_entities(
    data: ProtectData,
) -> list[BaseProtectEntity]:
    entities: list[BaseProtectEntity] = []
    device = data.api.bootstrap.nvr
    for description in NVR_SENSORS + NVR_DISABLED_SENSORS:
        entities.append(ProtectNVRSensor(data, device, description))
        _LOGGER.debug("Adding NVR sensor entity %s", description.name)

    return entities


class BaseProtectSensor(BaseProtectEntity, SensorEntity):
    """A UniFi Protect Sensor Entity."""

    entity_description: ProtectSensorEntityDescription
    _state_attrs = ("_attr_available", "_attr_native_value")

    def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
        super()._async_update_device_from_protect(device)
        self._attr_native_value = self.entity_description.get_ufp_value(self.device)


class ProtectDeviceSensor(BaseProtectSensor, ProtectDeviceEntity):
    """A Ubiquiti UniFi Protect Sensor."""


class ProtectNVRSensor(BaseProtectSensor, ProtectNVREntity):
    """A Ubiquiti UniFi Protect Sensor."""


class ProtectEventSensor(EventEntityMixin, SensorEntity):
    """A UniFi Protect Device Sensor with access tokens."""

    entity_description: ProtectSensorEventEntityDescription
    _state_attrs = (
        "_attr_available",
        "_attr_native_value",
        "_attr_extra_state_attributes",
    )
