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

from contextlib import suppress

from pyfibaro.fibaro_device import DeviceModel

from homeassistant.components.sensor import (
    ENTITY_ID_FORMAT,
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONCENTRATION_PARTS_PER_MILLION,
    LIGHT_LUX,
    PERCENTAGE,
    Platform,
    UnitOfEnergy,
    UnitOfPower,
    UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import convert

from . import FibaroController, FibaroDevice
from .const import DOMAIN

# List of known sensors which represents a fibaro device
MAIN_SENSOR_TYPES: dict[str, SensorEntityDescription] = {
    "com.fibaro.temperatureSensor": SensorEntityDescription(
        key="com.fibaro.temperatureSensor",
        name="Temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "com.fibaro.smokeSensor": SensorEntityDescription(
        key="com.fibaro.smokeSensor",
        name="Smoke",
        native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
        icon="mdi:fire",
    ),
    "CO2": SensorEntityDescription(
        key="CO2",
        name="CO2",
        native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
        device_class=SensorDeviceClass.CO2,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "com.fibaro.humiditySensor": SensorEntityDescription(
        key="com.fibaro.humiditySensor",
        name="Humidity",
        native_unit_of_measurement=PERCENTAGE,
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "com.fibaro.lightSensor": SensorEntityDescription(
        key="com.fibaro.lightSensor",
        name="Light",
        native_unit_of_measurement=LIGHT_LUX,
        device_class=SensorDeviceClass.ILLUMINANCE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "com.fibaro.energyMeter": SensorEntityDescription(
        key="com.fibaro.energyMeter",
        name="Energy",
        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
}

# List of additional sensors which are created based on a property
# The key is the property name
ADDITIONAL_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
    SensorEntityDescription(
        key="energy",
        name="Energy",
        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="power",
        name="Power",
        native_unit_of_measurement=UnitOfPower.WATT,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
)

FIBARO_TO_HASS_UNIT: dict[str, str] = {
    "lux": LIGHT_LUX,
    "C": UnitOfTemperature.CELSIUS,
    "F": UnitOfTemperature.FAHRENHEIT,
}


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the Fibaro controller devices."""
    entities: list[SensorEntity] = []

    controller: FibaroController = hass.data[DOMAIN][entry.entry_id]

    for device in controller.fibaro_devices[Platform.SENSOR]:
        entity_description = MAIN_SENSOR_TYPES.get(device.type)

        # main sensors are created even if the entity type is not known
        entities.append(FibaroSensor(device, entity_description))

    for platform in (
        Platform.BINARY_SENSOR,
        Platform.CLIMATE,
        Platform.COVER,
        Platform.LIGHT,
        Platform.LOCK,
        Platform.SENSOR,
        Platform.SWITCH,
    ):
        for device in controller.fibaro_devices[platform]:
            for entity_description in ADDITIONAL_SENSOR_TYPES:
                if entity_description.key in device.properties:
                    entities.append(FibaroAdditionalSensor(device, entity_description))

    async_add_entities(entities, True)


class FibaroSensor(FibaroDevice, SensorEntity):
    """Representation of a Fibaro Sensor."""

    def __init__(
        self,
        fibaro_device: DeviceModel,
        entity_description: SensorEntityDescription | None,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(fibaro_device)
        if entity_description is not None:
            self.entity_description = entity_description
        self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)

        # Map unit if it was not defined in the entity description
        # or there is no entity description at all
        with suppress(KeyError, ValueError):
            if not self.native_unit_of_measurement:
                self._attr_native_unit_of_measurement = FIBARO_TO_HASS_UNIT.get(
                    fibaro_device.unit, fibaro_device.unit
                )

    def update(self) -> None:
        """Update the state."""
        super().update()
        with suppress(TypeError):
            self._attr_native_value = self.fibaro_device.value.float_value()


class FibaroAdditionalSensor(FibaroDevice, SensorEntity):
    """Representation of a Fibaro Additional Sensor."""

    def __init__(
        self, fibaro_device: DeviceModel, entity_description: SensorEntityDescription
    ) -> None:
        """Initialize the sensor."""
        super().__init__(fibaro_device)
        self.entity_description = entity_description

        # To differentiate additional sensors from main sensors they need
        # to get different names and ids
        self.entity_id = ENTITY_ID_FORMAT.format(
            f"{self.ha_id}_{entity_description.key}"
        )
        self._attr_name = f"{fibaro_device.friendly_name} {entity_description.name}"
        self._attr_unique_id = f"{fibaro_device.unique_id_str}_{entity_description.key}"

    def update(self) -> None:
        """Update the state."""
        super().update()
        with suppress(KeyError, ValueError):
            self._attr_native_value = convert(
                self.fibaro_device.properties[self.entity_description.key],
                float,
            )