"""Sensor entities for the madVR integration."""

from collections.abc import Callable
from dataclasses import dataclass
from typing import override

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import (
    ASPECT_DEC,
    ASPECT_INT,
    ASPECT_NAME,
    ASPECT_RES,
    INCOMING_ASPECT_RATIO,
    INCOMING_BIT_DEPTH,
    INCOMING_BLACK_LEVELS,
    INCOMING_COLOR_SPACE,
    INCOMING_COLORIMETRY,
    INCOMING_FRAME_RATE,
    INCOMING_RES,
    INCOMING_SIGNAL_TYPE,
    MASKING_DEC,
    MASKING_INT,
    MASKING_RES,
    OUTGOING_BIT_DEPTH,
    OUTGOING_BLACK_LEVELS,
    OUTGOING_COLOR_SPACE,
    OUTGOING_COLORIMETRY,
    OUTGOING_FRAME_RATE,
    OUTGOING_RES,
    OUTGOING_SIGNAL_TYPE,
    TEMP_CPU,
    TEMP_GPU,
    TEMP_HDMI,
    TEMP_MAINBOARD,
)
from .coordinator import MadVRConfigEntry, MadVRCoordinator
from .entity import MadVREntity


def is_valid_temperature(value: float | None) -> bool:
    """Check if the temperature value is valid."""
    return value is not None and value > 0


def get_temperature(coordinator: MadVRCoordinator, key: str) -> float | None:
    """Get temperature value if valid, otherwise return None."""
    try:
        temp = float(coordinator.data.get(key, 0))
    except AttributeError, ValueError:
        return None
    else:
        return temp if is_valid_temperature(temp) else None


@dataclass(frozen=True, kw_only=True)
class MadvrSensorEntityDescription(SensorEntityDescription):
    """Describe madVR sensor entity."""

    value_fn: Callable[[MadVRCoordinator], StateType]


SENSORS: tuple[MadvrSensorEntityDescription, ...] = (
    MadvrSensorEntityDescription(
        key=TEMP_GPU,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value_fn=lambda coordinator: get_temperature(coordinator, TEMP_GPU),
        translation_key=TEMP_GPU,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=TEMP_HDMI,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value_fn=lambda coordinator: get_temperature(coordinator, TEMP_HDMI),
        translation_key=TEMP_HDMI,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=TEMP_CPU,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value_fn=lambda coordinator: get_temperature(coordinator, TEMP_CPU),
        translation_key=TEMP_CPU,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=TEMP_MAINBOARD,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value_fn=lambda coordinator: get_temperature(coordinator, TEMP_MAINBOARD),
        translation_key=TEMP_MAINBOARD,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_RES,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_RES),
        translation_key=INCOMING_RES,
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_SIGNAL_TYPE,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_SIGNAL_TYPE),
        translation_key=INCOMING_SIGNAL_TYPE,
        device_class=SensorDeviceClass.ENUM,
        options=["2D", "3D"],
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_FRAME_RATE,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_FRAME_RATE),
        translation_key=INCOMING_FRAME_RATE,
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_COLOR_SPACE,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_COLOR_SPACE),
        translation_key=INCOMING_COLOR_SPACE,
        device_class=SensorDeviceClass.ENUM,
        options=["RGB", "444", "422", "420"],
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_BIT_DEPTH,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_BIT_DEPTH),
        translation_key=INCOMING_BIT_DEPTH,
        device_class=SensorDeviceClass.ENUM,
        options=["8bit", "10bit", "12bit"],
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_COLORIMETRY,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_COLORIMETRY),
        translation_key=INCOMING_COLORIMETRY,
        device_class=SensorDeviceClass.ENUM,
        options=["SDR", "HDR10", "HLG 601", "PAL", "709", "DCI", "2020"],
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_BLACK_LEVELS,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_BLACK_LEVELS),
        translation_key=INCOMING_BLACK_LEVELS,
        device_class=SensorDeviceClass.ENUM,
        options=["TV", "PC"],
    ),
    MadvrSensorEntityDescription(
        key=INCOMING_ASPECT_RATIO,
        value_fn=lambda coordinator: coordinator.data.get(INCOMING_ASPECT_RATIO),
        translation_key=INCOMING_ASPECT_RATIO,
        device_class=SensorDeviceClass.ENUM,
        options=["16:9", "4:3"],
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_RES,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_RES),
        translation_key=OUTGOING_RES,
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_SIGNAL_TYPE,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_SIGNAL_TYPE),
        translation_key=OUTGOING_SIGNAL_TYPE,
        device_class=SensorDeviceClass.ENUM,
        options=["2D", "3D"],
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_FRAME_RATE,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_FRAME_RATE),
        translation_key=OUTGOING_FRAME_RATE,
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_COLOR_SPACE,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_COLOR_SPACE),
        translation_key=OUTGOING_COLOR_SPACE,
        device_class=SensorDeviceClass.ENUM,
        options=["RGB", "444", "422", "420"],
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_BIT_DEPTH,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_BIT_DEPTH),
        translation_key=OUTGOING_BIT_DEPTH,
        device_class=SensorDeviceClass.ENUM,
        options=["8bit", "10bit", "12bit"],
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_COLORIMETRY,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_COLORIMETRY),
        translation_key=OUTGOING_COLORIMETRY,
        device_class=SensorDeviceClass.ENUM,
        options=["SDR", "HDR10", "HLG 601", "PAL", "709", "DCI", "2020"],
    ),
    MadvrSensorEntityDescription(
        key=OUTGOING_BLACK_LEVELS,
        value_fn=lambda coordinator: coordinator.data.get(OUTGOING_BLACK_LEVELS),
        translation_key=OUTGOING_BLACK_LEVELS,
        device_class=SensorDeviceClass.ENUM,
        options=["TV", "PC"],
    ),
    MadvrSensorEntityDescription(
        key=ASPECT_RES,
        value_fn=lambda coordinator: coordinator.data.get(ASPECT_RES),
        translation_key=ASPECT_RES,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=ASPECT_DEC,
        value_fn=lambda coordinator: coordinator.data.get(ASPECT_DEC),
        translation_key=ASPECT_DEC,
    ),
    MadvrSensorEntityDescription(
        key=ASPECT_INT,
        value_fn=lambda coordinator: coordinator.data.get(ASPECT_INT),
        translation_key=ASPECT_INT,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=ASPECT_NAME,
        value_fn=lambda coordinator: coordinator.data.get(ASPECT_NAME),
        translation_key=ASPECT_NAME,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=MASKING_RES,
        value_fn=lambda coordinator: coordinator.data.get(MASKING_RES),
        translation_key=MASKING_RES,
        entity_registry_enabled_default=False,
    ),
    MadvrSensorEntityDescription(
        key=MASKING_DEC,
        value_fn=lambda coordinator: coordinator.data.get(MASKING_DEC),
        translation_key=MASKING_DEC,
    ),
    MadvrSensorEntityDescription(
        key=MASKING_INT,
        value_fn=lambda coordinator: coordinator.data.get(MASKING_INT),
        translation_key=MASKING_INT,
        entity_registry_enabled_default=False,
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: MadVRConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the sensor entities."""
    coordinator = entry.runtime_data
    async_add_entities(MadvrSensor(coordinator, description) for description in SENSORS)


class MadvrSensor(MadVREntity, SensorEntity):
    """Base class for madVR sensors."""

    def __init__(
        self,
        coordinator: MadVRCoordinator,
        description: MadvrSensorEntityDescription,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator)
        self.entity_description: MadvrSensorEntityDescription = description
        self._attr_unique_id = f"{coordinator.mac}_{description.key}"

    @property
    @override
    def native_value(self) -> float | str | None:
        """Return the state of the sensor."""
        val = self.entity_description.value_fn(self.coordinator)
        # check if sensor is enum
        if self.entity_description.device_class == SensorDeviceClass.ENUM:
            if (
                self.entity_description.options
                and val in self.entity_description.options
            ):
                return val
            # return None for values that are not in the options
            return None

        return val
