"""Support for Overkiz sensors."""
from __future__ import annotations

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

from pyoverkiz.enums import OverkizAttribute, OverkizState, UIWidget
from pyoverkiz.types import StateType as OverkizStateType

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONCENTRATION_PARTS_PER_MILLION,
    LIGHT_LUX,
    PERCENTAGE,
    SIGNAL_STRENGTH_DECIBELS,
    EntityCategory,
    UnitOfEnergy,
    UnitOfPower,
    UnitOfTemperature,
    UnitOfTime,
    UnitOfVolume,
    UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from . import HomeAssistantOverkizData
from .const import (
    DOMAIN,
    IGNORED_OVERKIZ_DEVICES,
    OVERKIZ_STATE_TO_TRANSLATION,
    OVERKIZ_UNIT_TO_HA,
)
from .coordinator import OverkizDataUpdateCoordinator
from .entity import OverkizDescriptiveEntity, OverkizEntity


@dataclass
class OverkizSensorDescription(SensorEntityDescription):
    """Class to describe an Overkiz sensor."""

    native_value: Callable[[OverkizStateType], StateType] | None = None


SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
    OverkizSensorDescription(
        key=OverkizState.CORE_BATTERY_LEVEL,
        name="Battery level",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
        entity_category=EntityCategory.DIAGNOSTIC,
        native_value=lambda value: int(float(str(value).strip("%"))),
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_BATTERY,
        name="Battery",
        entity_category=EntityCategory.DIAGNOSTIC,
        icon="mdi:battery",
        device_class=SensorDeviceClass.ENUM,
        options=["full", "normal", "medium", "low", "verylow"],
        translation_key="battery",
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_RSSI_LEVEL,
        name="RSSI level",
        native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        state_class=SensorStateClass.MEASUREMENT,
        native_value=lambda value: round(cast(float, value)),
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_EXPECTED_NUMBER_OF_SHOWER,
        name="Expected number of shower",
        icon="mdi:shower-head",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_NUMBER_OF_SHOWER_REMAINING,
        name="Number of shower remaining",
        icon="mdi:shower-head",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # V40 is measured in litres (L) and shows the amount of warm (mixed) water
    # with a temperature of 40 C, which can be drained from
    # a switched off electric water heater.
    OverkizSensorDescription(
        key=OverkizState.CORE_V40_WATER_VOLUME_ESTIMATION,
        name="Water volume estimation at 40 °C",
        icon="mdi:water",
        native_unit_of_measurement=UnitOfVolume.LITERS,
        device_class=SensorDeviceClass.VOLUME,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_WATER_CONSUMPTION,
        name="Water consumption",
        icon="mdi:water",
        native_unit_of_measurement=UnitOfVolume.LITERS,
        device_class=SensorDeviceClass.VOLUME,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.IO_OUTLET_ENGINE,
        name="Outlet engine",
        icon="mdi:fan-chevron-down",
        native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.IO_INLET_ENGINE,
        name="Inlet engine",
        icon="mdi:fan-chevron-up",
        native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.HLRRWIFI_ROOM_TEMPERATURE,
        name="Room temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
    ),
    OverkizSensorDescription(
        key=OverkizState.IO_MIDDLE_WATER_TEMPERATURE,
        name="Middle water temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_FOSSIL_ENERGY_CONSUMPTION,
        name="Fossil energy consumption",
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_GAS_CONSUMPTION,
        name="Gas consumption",
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_THERMAL_ENERGY_CONSUMPTION,
        name="Thermal energy consumption",
    ),
    # LightSensor/LuminanceSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_LUMINANCE,
        name="Luminance",
        device_class=SensorDeviceClass.ILLUMINANCE,
        # core:MeasuredValueType = core:LuminanceInLux
        native_unit_of_measurement=LIGHT_LUX,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # ElectricitySensor/CumulativeElectricPowerConsumptionSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_ELECTRIC_ENERGY_CONSUMPTION,
        name="Electric energy consumption",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        # (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        # core:MeasurementCategory attribute = electric/overall
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_ELECTRIC_POWER_CONSUMPTION,
        name="Electric power consumption",
        device_class=SensorDeviceClass.POWER,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        # (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
        native_unit_of_measurement=UnitOfPower.WATT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF1,
        name="Consumption tariff 1",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF2,
        name="Consumption tariff 2",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF3,
        name="Consumption tariff 3",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF4,
        name="Consumption tariff 4",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF5,
        name="Consumption tariff 5",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF6,
        name="Consumption tariff 6",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF7,
        name="Consumption tariff 7",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF8,
        name="Consumption tariff 8",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_CONSUMPTION_TARIFF9,
        name="Consumption tariff 9",
        device_class=SensorDeviceClass.ENERGY,
        # core:MeasuredValueType = core:ElectricalEnergyInWh
        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # HumiditySensor/RelativeHumiditySensor
    OverkizSensorDescription(
        key=OverkizState.CORE_RELATIVE_HUMIDITY,
        name="Relative humidity",
        native_value=lambda value: round(cast(float, value), 2),
        device_class=SensorDeviceClass.HUMIDITY,
        # core:MeasuredValueType = core:RelativeValueInPercentage
        native_unit_of_measurement=PERCENTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # TemperatureSensor/TemperatureSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_TEMPERATURE,
        name="Temperature",
        native_value=lambda value: round(cast(float, value), 2),
        device_class=SensorDeviceClass.TEMPERATURE,
        # core:MeasuredValueType = core:TemperatureInCelcius
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # WeatherSensor/WeatherForecastSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_WEATHER_STATUS,
        name="Weather status",
        device_class=SensorDeviceClass.TEMPERATURE,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_MINIMUM_TEMPERATURE,
        name="Minimum temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_MAXIMUM_TEMPERATURE,
        name="Maximum temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # AirSensor/COSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_CO_CONCENTRATION,
        name="CO concentration",
        device_class=SensorDeviceClass.CO,
        native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # AirSensor/CO2Sensor
    OverkizSensorDescription(
        key=OverkizState.CORE_CO2_CONCENTRATION,
        name="CO2 concentration",
        device_class=SensorDeviceClass.CO2,
        native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # SunSensor/SunEnergySensor
    OverkizSensorDescription(
        key=OverkizState.CORE_SUN_ENERGY,
        name="Sun energy",
        native_value=lambda value: round(cast(float, value), 2),
        icon="mdi:solar-power",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # WindSensor/WindSpeedSensor
    OverkizSensorDescription(
        key=OverkizState.CORE_WIND_SPEED,
        name="Wind speed",
        native_value=lambda value: round(cast(float, value), 2),
        icon="mdi:weather-windy",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    # SmokeSensor/SmokeSensor
    OverkizSensorDescription(
        key=OverkizState.IO_SENSOR_ROOM,
        name="Sensor room",
        device_class=SensorDeviceClass.ENUM,
        options=["clean", "dirty"],
        entity_category=EntityCategory.DIAGNOSTIC,
        icon="mdi:spray-bottle",
        translation_key="sensor_room",
    ),
    OverkizSensorDescription(
        key=OverkizState.IO_PRIORITY_LOCK_ORIGINATOR,
        name="Priority lock originator",
        icon="mdi:lock",
        entity_registry_enabled_default=False,
        translation_key="priority_lock_originator",
        native_value=lambda value: OVERKIZ_STATE_TO_TRANSLATION.get(
            cast(str, value), cast(str, value)
        ),
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_PRIORITY_LOCK_TIMER,
        name="Priority lock timer",
        icon="mdi:lock-clock",
        native_unit_of_measurement=UnitOfTime.SECONDS,
        entity_registry_enabled_default=False,
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_DISCRETE_RSSI_LEVEL,
        name="Discrete RSSI level",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        icon="mdi:wifi",
        device_class=SensorDeviceClass.ENUM,
        options=["verylow", "low", "normal", "good"],
        translation_key="discrete_rssi_level",
    ),
    OverkizSensorDescription(
        key=OverkizState.CORE_SENSOR_DEFECT,
        name="Sensor defect",
        entity_registry_enabled_default=False,
        entity_category=EntityCategory.DIAGNOSTIC,
        translation_key="sensor_defect",
        native_value=lambda value: OVERKIZ_STATE_TO_TRANSLATION.get(
            cast(str, value), cast(str, value)
        ),
    ),
    # DomesticHotWaterProduction/WaterHeatingSystem
    OverkizSensorDescription(
        key=OverkizState.IO_HEAT_PUMP_OPERATING_TIME,
        name="Heat pump operating time",
        device_class=SensorDeviceClass.DURATION,
        entity_category=EntityCategory.DIAGNOSTIC,
        native_unit_of_measurement=UnitOfTime.SECONDS,
    ),
    OverkizSensorDescription(
        key=OverkizState.IO_ELECTRIC_BOOSTER_OPERATING_TIME,
        name="Electric booster operating time",
        device_class=SensorDeviceClass.DURATION,
        native_unit_of_measurement=UnitOfTime.SECONDS,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    # Cover
    OverkizSensorDescription(
        key=OverkizState.CORE_TARGET_CLOSURE,
        name="Target closure",
        native_unit_of_measurement=PERCENTAGE,
        entity_registry_enabled_default=False,
    ),
    # ThreeWayWindowHandle/WindowHandle
    OverkizSensorDescription(
        key=OverkizState.CORE_THREE_WAY_HANDLE_DIRECTION,
        name="Three way handle direction",
        device_class=SensorDeviceClass.ENUM,
        options=["open", "tilt", "closed"],
        translation_key="three_way_handle_direction",
    ),
]

SUPPORTED_STATES = {description.key: description for description in SENSOR_DESCRIPTIONS}


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the Overkiz sensors from a config entry."""
    data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id]
    entities: list[SensorEntity] = []

    for device in data.coordinator.data.values():
        if device.widget == UIWidget.HOMEKIT_STACK:
            entities.append(
                OverkizHomeKitSetupCodeSensor(
                    device.device_url,
                    data.coordinator,
                )
            )

        if (
            device.widget in IGNORED_OVERKIZ_DEVICES
            or device.ui_class in IGNORED_OVERKIZ_DEVICES
        ):
            continue

        for state in device.definition.states:
            if description := SUPPORTED_STATES.get(state.qualified_name):
                entities.append(
                    OverkizStateSensor(
                        device.device_url,
                        data.coordinator,
                        description,
                    )
                )

    async_add_entities(entities)


class OverkizStateSensor(OverkizDescriptiveEntity, SensorEntity):
    """Representation of an Overkiz Sensor."""

    entity_description: OverkizSensorDescription

    @property
    def native_value(self) -> StateType:
        """Return the value of the sensor."""
        state = self.device.states.get(self.entity_description.key)

        if not state or not state.value:
            return None

        # Transform the value with a lambda function
        if self.entity_description.native_value:
            return self.entity_description.native_value(state.value)

        if isinstance(state.value, (dict, list)):
            return None

        return state.value

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return the unit of measurement."""
        if (
            not (default_unit := self.entity_description.native_unit_of_measurement)
            or not (state := self.device.states.get(self.entity_description.key))
            or not state.value
        ):
            return default_unit

        attrs = self.device.attributes
        if (unit := attrs[f"{state.name}MeasuredValueType"]) and (
            unit_value := unit.value_as_str
        ):
            return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)

        if (unit := attrs[OverkizAttribute.CORE_MEASURED_VALUE_TYPE]) and (
            unit_value := unit.value_as_str
        ):
            return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)

        return default_unit


class OverkizHomeKitSetupCodeSensor(OverkizEntity, SensorEntity):
    """Representation of an Overkiz HomeKit Setup Code."""

    _attr_icon = "mdi:shield-home"
    _attr_entity_category = EntityCategory.DIAGNOSTIC

    def __init__(
        self, device_url: str, coordinator: OverkizDataUpdateCoordinator
    ) -> None:
        """Initialize the device."""
        super().__init__(device_url, coordinator)
        self._attr_name = "HomeKit setup code"

    @property
    def native_value(self) -> str | None:
        """Return the value of the sensor."""
        if state := self.device.attributes.get(OverkizAttribute.HOMEKIT_SETUP_CODE):
            return cast(str, state.value)
        return None

    @property
    def device_info(self) -> DeviceInfo:
        """Return device registry information for this entity."""
        # By default this sensor will be listed at a virtual HomekitStack device,
        # but it makes more sense to show this at the gateway device
        # in the entity registry.
        return DeviceInfo(
            identifiers={(DOMAIN, self.executor.get_gateway_id())},
        )
