"""Support for DHT and DS18B20 sensors attached to a Konnected device."""
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import (
    CONF_DEVICES,
    CONF_NAME,
    CONF_SENSORS,
    CONF_TYPE,
    CONF_ZONE,
    DEVICE_CLASS_HUMIDITY,
    DEVICE_CLASS_TEMPERATURE,
    PERCENTAGE,
    TEMP_CELSIUS,
)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW

SENSOR_TYPES = {
    DEVICE_CLASS_TEMPERATURE: ["Temperature", TEMP_CELSIUS],
    DEVICE_CLASS_HUMIDITY: ["Humidity", PERCENTAGE],
}


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up sensors attached to a Konnected device from a config entry."""
    data = hass.data[KONNECTED_DOMAIN]
    device_id = config_entry.data["id"]
    sensors = []

    # Initialize all DHT sensors.
    dht_sensors = [
        sensor
        for sensor in data[CONF_DEVICES][device_id][CONF_SENSORS]
        if sensor[CONF_TYPE] == "dht"
    ]
    for sensor in dht_sensors:
        sensors.append(KonnectedSensor(device_id, sensor, DEVICE_CLASS_TEMPERATURE))
        sensors.append(KonnectedSensor(device_id, sensor, DEVICE_CLASS_HUMIDITY))

    async_add_entities(sensors)

    @callback
    def async_add_ds18b20(attrs):
        """Add new KonnectedSensor representing a ds18b20 sensor."""
        sensor_config = next(
            (
                s
                for s in data[CONF_DEVICES][device_id][CONF_SENSORS]
                if s[CONF_TYPE] == "ds18b20" and s[CONF_ZONE] == attrs.get(CONF_ZONE)
            ),
            None,
        )

        async_add_entities(
            [
                KonnectedSensor(
                    device_id,
                    sensor_config,
                    DEVICE_CLASS_TEMPERATURE,
                    addr=attrs.get("addr"),
                    initial_state=attrs.get("temp"),
                )
            ],
            True,
        )

    # DS18B20 sensors entities are initialized when they report for the first
    # time. Set up a listener for that signal from the Konnected component.
    async_dispatcher_connect(hass, SIGNAL_DS18B20_NEW, async_add_ds18b20)


class KonnectedSensor(SensorEntity):
    """Represents a Konnected DHT Sensor."""

    def __init__(self, device_id, data, sensor_type, addr=None, initial_state=None):
        """Initialize the entity for a single sensor_type."""
        self._addr = addr
        self._data = data
        self._device_id = device_id
        self._type = sensor_type
        self._zone_num = self._data.get(CONF_ZONE)
        self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
        self._unique_id = addr or f"{device_id}-{self._zone_num}-{sensor_type}"

        # set initial state if known at initialization
        self._state = initial_state
        if self._state:
            self._state = round(float(self._state), 1)

        # set entity name if given
        self._name = self._data.get(CONF_NAME)
        if self._name:
            self._name += f" {SENSOR_TYPES[sensor_type][0]}"

    @property
    def unique_id(self) -> str:
        """Return the unique id."""
        return self._unique_id

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def native_value(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def native_unit_of_measurement(self):
        """Return the unit of measurement."""
        return self._unit_of_measurement

    @property
    def device_info(self):
        """Return the device info."""
        return {"identifiers": {(KONNECTED_DOMAIN, self._device_id)}}

    async def async_added_to_hass(self):
        """Store entity_id and register state change callback."""
        entity_id_key = self._addr or self._type
        self._data[entity_id_key] = self.entity_id
        async_dispatcher_connect(
            self.hass, f"konnected.{self.entity_id}.update", self.async_set_state
        )

    @callback
    def async_set_state(self, state):
        """Update the sensor's state."""
        if self._type == DEVICE_CLASS_HUMIDITY:
            self._state = int(float(state))
        else:
            self._state = round(float(state), 1)
        self.async_write_ha_state()
