"""Support for monitoring a Sense energy sensor."""
from datetime import timedelta
import logging

from sense_energy import SenseAPITimeoutException

from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR, POWER_WATT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

from .const import (
    ACTIVE_NAME,
    ACTIVE_TYPE,
    CONSUMPTION_ID,
    CONSUMPTION_NAME,
    DOMAIN,
    ICON,
    MDI_ICONS,
    PRODUCTION_ID,
    PRODUCTION_NAME,
    SENSE_DATA,
    SENSE_DEVICE_UPDATE,
    SENSE_DEVICES_DATA,
    SENSE_DISCOVERED_DEVICES_DATA,
)

MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300)


_LOGGER = logging.getLogger(__name__)


class SensorConfig:
    """Data structure holding sensor configuration."""

    def __init__(self, name, sensor_type):
        """Sensor name and type to pass to API."""
        self.name = name
        self.sensor_type = sensor_type


# Sensor types/ranges
ACTIVE_SENSOR_TYPE = SensorConfig(ACTIVE_NAME, ACTIVE_TYPE)

# Sensor types/ranges
TRENDS_SENSOR_TYPES = {
    "daily": SensorConfig("Daily", "DAY"),
    "weekly": SensorConfig("Weekly", "WEEK"),
    "monthly": SensorConfig("Monthly", "MONTH"),
    "yearly": SensorConfig("Yearly", "YEAR"),
}

# Production/consumption variants
SENSOR_VARIANTS = [PRODUCTION_ID, CONSUMPTION_ID]


def sense_to_mdi(sense_icon):
    """Convert sense icon to mdi icon."""
    return "mdi:{}".format(MDI_ICONS.get(sense_icon, "power-plug"))


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up the Sense sensor."""
    data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DATA]
    sense_devices_data = hass.data[DOMAIN][config_entry.entry_id][SENSE_DEVICES_DATA]

    @Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES)
    async def update_trends():
        """Update the daily power usage."""
        await data.update_trend_data()

    sense_monitor_id = data.sense_monitor_id
    sense_devices = hass.data[DOMAIN][config_entry.entry_id][
        SENSE_DISCOVERED_DEVICES_DATA
    ]
    await data.update_trend_data()

    devices = [
        SenseEnergyDevice(sense_devices_data, device, sense_monitor_id)
        for device in sense_devices
        if device["tags"]["DeviceListAllowed"] == "true"
    ]

    for var in SENSOR_VARIANTS:
        name = ACTIVE_SENSOR_TYPE.name
        sensor_type = ACTIVE_SENSOR_TYPE.sensor_type
        is_production = var == PRODUCTION_ID

        unique_id = f"{sense_monitor_id}-active-{var}"
        devices.append(
            SenseActiveSensor(
                data, name, sensor_type, is_production, sense_monitor_id, var, unique_id
            )
        )

    for type_id in TRENDS_SENSOR_TYPES:
        typ = TRENDS_SENSOR_TYPES[type_id]
        for var in SENSOR_VARIANTS:
            name = typ.name
            sensor_type = typ.sensor_type
            is_production = var == PRODUCTION_ID

            unique_id = f"{sense_monitor_id}-{type_id}-{var}"
            devices.append(
                SenseTrendsSensor(
                    data,
                    name,
                    sensor_type,
                    is_production,
                    update_trends,
                    var,
                    unique_id,
                )
            )

    async_add_entities(devices)


class SenseActiveSensor(Entity):
    """Implementation of a Sense energy sensor."""

    def __init__(
        self,
        data,
        name,
        sensor_type,
        is_production,
        sense_monitor_id,
        sensor_id,
        unique_id,
    ):
        """Initialize the Sense sensor."""
        name_type = PRODUCTION_NAME if is_production else CONSUMPTION_NAME
        self._name = f"{name} {name_type}"
        self._unique_id = unique_id
        self._available = False
        self._data = data
        self._sense_monitor_id = sense_monitor_id
        self._sensor_type = sensor_type
        self._is_production = is_production
        self._state = None
        self._undo_dispatch_subscription = None

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

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

    @property
    def available(self):
        """Return the availability of the sensor."""
        return self._available

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity, if any."""
        return POWER_WATT

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return ICON

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

    @property
    def should_poll(self):
        """Return the device should not poll for updates."""
        return False

    async def async_added_to_hass(self):
        """Register callbacks."""
        self._undo_dispatch_subscription = async_dispatcher_connect(
            self.hass,
            f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
            self._async_update_from_data,
        )

    async def async_will_remove_from_hass(self):
        """Undo subscription."""
        if self._undo_dispatch_subscription:
            self._undo_dispatch_subscription()

    @callback
    def _async_update_from_data(self):
        """Update the sensor from the data. Must not do I/O."""
        self._state = round(
            self._data.active_solar_power
            if self._is_production
            else self._data.active_power
        )
        self._available = True
        self.async_write_ha_state()


class SenseTrendsSensor(Entity):
    """Implementation of a Sense energy sensor."""

    def __init__(
        self, data, name, sensor_type, is_production, update_call, sensor_id, unique_id
    ):
        """Initialize the Sense sensor."""
        name_type = PRODUCTION_NAME if is_production else CONSUMPTION_NAME
        self._name = f"{name} {name_type}"
        self._unique_id = unique_id
        self._available = False
        self._data = data
        self._sensor_type = sensor_type
        self.update_sensor = update_call
        self._is_production = is_production
        self._state = None
        self._unit_of_measurement = ENERGY_KILO_WATT_HOUR

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

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

    @property
    def available(self):
        """Return the availability of the sensor."""
        return self._available

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity, if any."""
        return self._unit_of_measurement

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return ICON

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

    async def async_update(self):
        """Get the latest data, update state."""

        try:
            await self.update_sensor()
        except SenseAPITimeoutException:
            _LOGGER.error("Timeout retrieving data")
            return

        state = self._data.get_trend(self._sensor_type, self._is_production)
        self._state = round(state, 1)
        self._available = True


class SenseEnergyDevice(Entity):
    """Implementation of a Sense energy device."""

    def __init__(self, sense_devices_data, device, sense_monitor_id):
        """Initialize the Sense binary sensor."""
        self._name = f"{device['name']} {CONSUMPTION_NAME}"
        self._id = device["id"]
        self._available = False
        self._sense_monitor_id = sense_monitor_id
        self._unique_id = f"{sense_monitor_id}-{self._id}-{CONSUMPTION_ID}"
        self._icon = sense_to_mdi(device["icon"])
        self._sense_devices_data = sense_devices_data
        self._undo_dispatch_subscription = None
        self._state = None

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

    @property
    def available(self):
        """Return the availability of the sensor."""
        return self._available

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

    @property
    def unique_id(self):
        """Return the unique id of the power sensor."""
        return self._unique_id

    @property
    def icon(self):
        """Return the icon of the power sensor."""
        return self._icon

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement of this entity."""
        return POWER_WATT

    @property
    def device_class(self):
        """Return the device class of the power sensor."""
        return DEVICE_CLASS_POWER

    @property
    def should_poll(self):
        """Return the device should not poll for updates."""
        return False

    async def async_added_to_hass(self):
        """Register callbacks."""
        self._undo_dispatch_subscription = async_dispatcher_connect(
            self.hass,
            f"{SENSE_DEVICE_UPDATE}-{self._sense_monitor_id}",
            self._async_update_from_data,
        )

    async def async_will_remove_from_hass(self):
        """Undo subscription."""
        if self._undo_dispatch_subscription:
            self._undo_dispatch_subscription()

    @callback
    def _async_update_from_data(self):
        """Get the latest data, update state. Must not do I/O."""
        device_data = self._sense_devices_data.get_device_by_id(self._id)
        if not device_data or "w" not in device_data:
            self._state = 0
        else:
            self._state = int(device_data["w"])
        self._available = True
        self.async_write_ha_state()
