"""Support for Synology NAS Sensors."""
from datetime import timedelta
import logging

from SynologyDSM import SynologyDSM
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_ATTRIBUTION,
    CONF_DISKS,
    CONF_HOST,
    CONF_MONITORED_CONDITIONS,
    CONF_NAME,
    CONF_PASSWORD,
    CONF_PORT,
    CONF_SSL,
    CONF_USERNAME,
    EVENT_HOMEASSISTANT_START,
    TEMP_CELSIUS,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

_LOGGER = logging.getLogger(__name__)

ATTRIBUTION = "Data provided by Synology"

CONF_VOLUMES = "volumes"
DEFAULT_NAME = "Synology DSM"
DEFAULT_PORT = 5001

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)

_UTILISATION_MON_COND = {
    "cpu_other_load": ["CPU Load (Other)", "%", "mdi:chip"],
    "cpu_user_load": ["CPU Load (User)", "%", "mdi:chip"],
    "cpu_system_load": ["CPU Load (System)", "%", "mdi:chip"],
    "cpu_total_load": ["CPU Load (Total)", "%", "mdi:chip"],
    "cpu_1min_load": ["CPU Load (1 min)", "%", "mdi:chip"],
    "cpu_5min_load": ["CPU Load (5 min)", "%", "mdi:chip"],
    "cpu_15min_load": ["CPU Load (15 min)", "%", "mdi:chip"],
    "memory_real_usage": ["Memory Usage (Real)", "%", "mdi:memory"],
    "memory_size": ["Memory Size", "Mb", "mdi:memory"],
    "memory_cached": ["Memory Cached", "Mb", "mdi:memory"],
    "memory_available_swap": ["Memory Available (Swap)", "Mb", "mdi:memory"],
    "memory_available_real": ["Memory Available (Real)", "Mb", "mdi:memory"],
    "memory_total_swap": ["Memory Total (Swap)", "Mb", "mdi:memory"],
    "memory_total_real": ["Memory Total (Real)", "Mb", "mdi:memory"],
    "network_up": ["Network Up", "Kbps", "mdi:upload"],
    "network_down": ["Network Down", "Kbps", "mdi:download"],
}
_STORAGE_VOL_MON_COND = {
    "volume_status": ["Status", None, "mdi:checkbox-marked-circle-outline"],
    "volume_device_type": ["Type", None, "mdi:harddisk"],
    "volume_size_total": ["Total Size", None, "mdi:chart-pie"],
    "volume_size_used": ["Used Space", None, "mdi:chart-pie"],
    "volume_percentage_used": ["Volume Used", "%", "mdi:chart-pie"],
    "volume_disk_temp_avg": ["Average Disk Temp", None, "mdi:thermometer"],
    "volume_disk_temp_max": ["Maximum Disk Temp", None, "mdi:thermometer"],
}
_STORAGE_DSK_MON_COND = {
    "disk_name": ["Name", None, "mdi:harddisk"],
    "disk_device": ["Device", None, "mdi:dots-horizontal"],
    "disk_smart_status": ["Status (Smart)", None, "mdi:checkbox-marked-circle-outline"],
    "disk_status": ["Status", None, "mdi:checkbox-marked-circle-outline"],
    "disk_exceed_bad_sector_thr": ["Exceeded Max Bad Sectors", None, "mdi:test-tube"],
    "disk_below_remain_life_thr": ["Below Min Remaining Life", None, "mdi:test-tube"],
    "disk_temp": ["Temperature", None, "mdi:thermometer"],
}

_MONITORED_CONDITIONS = (
    list(_UTILISATION_MON_COND.keys())
    + list(_STORAGE_VOL_MON_COND.keys())
    + list(_STORAGE_DSK_MON_COND.keys())
)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Required(CONF_HOST): cv.string,
        vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_SSL, default=True): cv.boolean,
        vol.Required(CONF_USERNAME): cv.string,
        vol.Required(CONF_PASSWORD): cv.string,
        vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(
            cv.ensure_list, [vol.In(_MONITORED_CONDITIONS)]
        ),
        vol.Optional(CONF_DISKS): cv.ensure_list,
        vol.Optional(CONF_VOLUMES): cv.ensure_list,
    }
)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Synology NAS Sensor."""

    def run_setup(event):
        """Wait until Home Assistant is fully initialized before creating.

        Delay the setup until Home Assistant is fully initialized.
        This allows any entities to be created already
        """
        name = config.get(CONF_NAME)
        host = config.get(CONF_HOST)
        port = config.get(CONF_PORT)
        username = config.get(CONF_USERNAME)
        password = config.get(CONF_PASSWORD)
        use_ssl = config.get(CONF_SSL)
        unit = hass.config.units.temperature_unit
        monitored_conditions = config.get(CONF_MONITORED_CONDITIONS)

        api = SynoApi(host, port, username, password, unit, use_ssl)

        sensors = [
            SynoNasUtilSensor(api, name, variable, _UTILISATION_MON_COND[variable])
            for variable in monitored_conditions
            if variable in _UTILISATION_MON_COND
        ]

        # Handle all volumes
        if api.storage.volumes is not None:
            for volume in config.get(CONF_VOLUMES, api.storage.volumes):
                sensors += [
                    SynoNasStorageSensor(
                        api, name, variable, _STORAGE_VOL_MON_COND[variable], volume
                    )
                    for variable in monitored_conditions
                    if variable in _STORAGE_VOL_MON_COND
                ]

        # Handle all disks
        if api.storage.disks is not None:
            for disk in config.get(CONF_DISKS, api.storage.disks):
                sensors += [
                    SynoNasStorageSensor(
                        api, name, variable, _STORAGE_DSK_MON_COND[variable], disk
                    )
                    for variable in monitored_conditions
                    if variable in _STORAGE_DSK_MON_COND
                ]

        add_entities(sensors, True)

    # Wait until start event is sent to load this component.
    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)


class SynoApi:
    """Class to interface with Synology DSM API."""

    def __init__(self, host, port, username, password, temp_unit, use_ssl):
        """Initialize the API wrapper class."""

        self.temp_unit = temp_unit

        try:
            self._api = SynologyDSM(host, port, username, password, use_https=use_ssl)
        except:  # noqa: E722 pylint: disable=bare-except
            _LOGGER.error("Error setting up Synology DSM")

        # Will be updated when update() gets called.
        self.utilisation = self._api.utilisation
        self.storage = self._api.storage

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Update function for updating api information."""
        self._api.update()


class SynoNasSensor(Entity):
    """Representation of a Synology NAS Sensor."""

    def __init__(self, api, name, variable, variable_info, monitor_device=None):
        """Initialize the sensor."""
        self.var_id = variable
        self.var_name = "{} {}".format(name, variable_info[0])
        self.var_units = variable_info[1]
        self.var_icon = variable_info[2]
        self.monitor_device = monitor_device
        self._api = api

    @property
    def name(self):
        """Return the name of the sensor, if any."""
        if self.monitor_device is not None:
            return f"{self.var_name} ({self.monitor_device})"
        return self.var_name

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

    @property
    def unit_of_measurement(self):
        """Return the unit the value is expressed in."""
        if self.var_id in ["volume_disk_temp_avg", "volume_disk_temp_max", "disk_temp"]:
            return self._api.temp_unit
        return self.var_units

    def update(self):
        """Get the latest data for the states."""
        if self._api is not None:
            self._api.update()

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        return {ATTR_ATTRIBUTION: ATTRIBUTION}


class SynoNasUtilSensor(SynoNasSensor):
    """Representation a Synology Utilisation Sensor."""

    @property
    def state(self):
        """Return the state of the sensor."""
        network_sensors = ["network_up", "network_down"]
        memory_sensors = [
            "memory_size",
            "memory_cached",
            "memory_available_swap",
            "memory_available_real",
            "memory_total_swap",
            "memory_total_real",
        ]

        if self.var_id in network_sensors or self.var_id in memory_sensors:
            attr = getattr(self._api.utilisation, self.var_id)(False)

            if self.var_id in network_sensors:
                return round(attr / 1024.0, 1)
            if self.var_id in memory_sensors:
                return round(attr / 1024.0 / 1024.0, 1)
        else:
            return getattr(self._api.utilisation, self.var_id)


class SynoNasStorageSensor(SynoNasSensor):
    """Representation a Synology Utilisation Sensor."""

    @property
    def state(self):
        """Return the state of the sensor."""
        temp_sensors = ["volume_disk_temp_avg", "volume_disk_temp_max", "disk_temp"]

        if self.monitor_device is not None:
            if self.var_id in temp_sensors:
                attr = getattr(self._api.storage, self.var_id)(self.monitor_device)

                if attr is None:
                    return None

                if self._api.temp_unit == TEMP_CELSIUS:
                    return attr

                return round(attr * 1.8 + 32.0, 1)

            return getattr(self._api.storage, self.var_id)(self.monitor_device)
