"""
Support for QNAP NAS Sensors.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.qnap/
"""

import logging
from datetime import timedelta

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

import voluptuous as vol

REQUIREMENTS = ['qnapstats==0.2.3']

_LOGGER = logging.getLogger(__name__)

ATTR_DRIVE = 'Drive'
ATTR_DRIVE_SIZE = 'Drive Size'
ATTR_IP = 'IP Address'
ATTR_MAC = 'MAC Address'
ATTR_MASK = 'Mask'
ATTR_MAX_SPEED = 'Max Speed'
ATTR_MEMORY_SIZE = 'Memory Size'
ATTR_MODEL = 'Model'
ATTR_NAME = 'Name'
ATTR_PACKETS_TX = 'Packets (TX)'
ATTR_PACKETS_RX = 'Packets (RX)'
ATTR_PACKETS_ERR = 'Packets (Err)'
ATTR_SERIAL = 'Serial #'
ATTR_TYPE = 'Type'
ATTR_UPTIME = 'Uptime'
ATTR_VOLUME_SIZE = 'Volume Size'

CONF_DRIVES = 'drives'
CONF_NICS = 'nics'
CONF_VOLUMES = 'volumes'
DEFAULT_NAME = 'QNAP'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 5

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)

NOTIFICATION_ID = 'qnap_notification'
NOTIFICATION_TITLE = 'QNAP Sensor Setup'

_SYSTEM_MON_COND = {
    'status': ['Status', None, 'mdi:checkbox-marked-circle-outline'],
    'system_temp': ['System Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
}
_CPU_MON_COND = {
    'cpu_temp': ['CPU Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
    'cpu_usage': ['CPU Usage', '%', 'mdi:chip'],
}
_MEMORY_MON_COND = {
    'memory_free': ['Memory Available', 'GB', 'mdi:memory'],
    'memory_used': ['Memory Used', 'GB', 'mdi:memory'],
    'memory_percent_used': ['Memory Usage', '%', 'mdi:memory'],
}
_NETWORK_MON_COND = {
    'network_link_status': ['Network Link', None,
                            'mdi:checkbox-marked-circle-outline'],
    'network_tx': ['Network Up', 'MB/s', 'mdi:upload'],
    'network_rx': ['Network Down', 'MB/s', 'mdi:download'],
}
_DRIVE_MON_COND = {
    'drive_smart_status': ['SMART Status', None,
                           'mdi:checkbox-marked-circle-outline'],
    'drive_temp': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
}
_VOLUME_MON_COND = {
    'volume_size_used': ['Used Space', 'GB', 'mdi:chart-pie'],
    'volume_size_free': ['Free Space', 'GB', 'mdi:chart-pie'],
    'volume_percentage_used': ['Volume Used', '%', 'mdi:chart-pie'],
}

_MONITORED_CONDITIONS = list(_SYSTEM_MON_COND.keys()) + \
                        list(_CPU_MON_COND.keys()) + \
                        list(_MEMORY_MON_COND.keys()) + \
                        list(_NETWORK_MON_COND.keys()) + \
                        list(_DRIVE_MON_COND.keys()) + \
                        list(_VOLUME_MON_COND.keys())

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Optional(CONF_SSL, default=False): cv.boolean,
    vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
    vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
    vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
    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_NICS, default=None): cv.ensure_list,
    vol.Optional(CONF_DRIVES, default=None): cv.ensure_list,
    vol.Optional(CONF_VOLUMES, default=None): cv.ensure_list,
})


# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the QNAP NAS sensor."""
    api = QNAPStatsAPI(config)
    api.update()

    if not api.data:
        import homeassistant.loader as loader
        loader.get_component('persistent_notification').create(
            hass, 'Error: Failed to set up QNAP sensor.<br />'
                  'Check the logs for additional information. '
                  'You will need to restart hass after fixing.',
            title=NOTIFICATION_TITLE,
            notification_id=NOTIFICATION_ID)
        return False

    sensors = []

    # Basic sensors
    for variable in config[CONF_MONITORED_CONDITIONS]:
        if variable in _SYSTEM_MON_COND:
            sensors.append(QNAPSystemSensor(api, variable,
                                            _SYSTEM_MON_COND[variable]))
        if variable in _CPU_MON_COND:
            sensors.append(QNAPCPUSensor(api, variable,
                                         _CPU_MON_COND[variable]))
        if variable in _MEMORY_MON_COND:
            sensors.append(QNAPMemorySensor(api, variable,
                                            _MEMORY_MON_COND[variable]))

    # Network sensors
    nics = config[CONF_NICS]
    if nics is None:
        nics = api.data["system_stats"]["nics"].keys()

    for nic in nics:
        sensors += [QNAPNetworkSensor(api, variable,
                                      _NETWORK_MON_COND[variable], nic)
                    for variable in config[CONF_MONITORED_CONDITIONS]
                    if variable in _NETWORK_MON_COND]

    # Drive sensors
    drives = config[CONF_DRIVES]
    if drives is None:
        drives = api.data["smart_drive_health"].keys()

    for drive in drives:
        sensors += [QNAPDriveSensor(api, variable,
                                    _DRIVE_MON_COND[variable], drive)
                    for variable in config[CONF_MONITORED_CONDITIONS]
                    if variable in _DRIVE_MON_COND]

    # Volume sensors
    volumes = config[CONF_VOLUMES]
    if volumes is None:
        volumes = api.data["volumes"].keys()

    for volume in volumes:
        sensors += [QNAPVolumeSensor(api, variable,
                                     _VOLUME_MON_COND[variable], volume)
                    for variable in config[CONF_MONITORED_CONDITIONS]
                    if variable in _VOLUME_MON_COND]

    add_devices(sensors)


def round_nicely(number):
    """Round a number based on its size (so it looks nice)."""
    if number < 10:
        return round(number, 2)
    if number < 100:
        return round(number, 1)

    return round(number)


class QNAPStatsAPI(object):
    """Class to interface with the API."""

    def __init__(self, config):
        """Initialize the API wrapper."""
        from qnapstats import QNAPStats

        protocol = "https" if config.get(CONF_SSL) else "http"
        self._api = QNAPStats(
            protocol + "://" + config.get(CONF_HOST),
            config.get(CONF_PORT),
            config.get(CONF_USERNAME),
            config.get(CONF_PASSWORD),
            verify_ssl=config.get(CONF_VERIFY_SSL),
            timeout=config.get(CONF_TIMEOUT),
        )

        self.data = {}

    # pylint: disable=bare-except
    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Update API information and store locally."""
        try:
            self.data["system_stats"] = self._api.get_system_stats()
            self.data["system_health"] = self._api.get_system_health()
            self.data["smart_drive_health"] = self._api.get_smart_disk_health()
            self.data["volumes"] = self._api.get_volumes()
            self.data["bandwidth"] = self._api.get_bandwidth()
        except:
            _LOGGER.exception("Failed to fetch QNAP stats from the NAS.")


class QNAPSensor(Entity):
    """Base class for a QNAP sensor."""

    def __init__(self, api, variable, variable_info, monitor_device=None):
        """Initialize the sensor."""
        self.var_id = variable
        self.var_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."""
        server_name = self._api.data["system_stats"]["system"]["name"]

        if self.monitor_device is not None:
            return "{} {} ({})".format(server_name,
                                       self.var_name,
                                       self.monitor_device)
        else:
            return "{} {}".format(server_name,
                                  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."""
        return self.var_units

    def update(self):
        """Get the latest data for the states."""
        self._api.update()


class QNAPCPUSensor(QNAPSensor):
    """A QNAP sensor that monitors CPU stats."""

    @property
    def state(self):
        """Return the state of the sensor."""
        if self.var_id == "cpu_temp":
            return self._api.data["system_stats"]["cpu"]["temp_c"]
        elif self.var_id == "cpu_usage":
            return self._api.data["system_stats"]["cpu"]["usage_percent"]


class QNAPMemorySensor(QNAPSensor):
    """A QNAP sensor that monitors memory stats."""

    @property
    def state(self):
        """Return the state of the sensor."""
        free = float(self._api.data["system_stats"]["memory"]["free"]) / 1024
        if self.var_id == "memory_free":
            return round_nicely(free)

        total = float(self._api.data["system_stats"]["memory"]["total"]) / 1024

        used = total - free
        if self.var_id == "memory_used":
            return round_nicely(used)

        if self.var_id == "memory_percent_used":
            return round(used / total * 100)

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        if self._api.data:
            data = self._api.data["system_stats"]["memory"]
            size = round_nicely(float(data["total"]) / 1024)
            return {
                ATTR_MEMORY_SIZE: "{} GB".format(size),
            }


class QNAPNetworkSensor(QNAPSensor):
    """A QNAP sensor that monitors network stats."""

    @property
    def state(self):
        """Return the state of the sensor."""
        if self.var_id == "network_link_status":
            nic = self._api.data["system_stats"]["nics"][self.monitor_device]
            return nic["link_status"]

        data = self._api.data["bandwidth"][self.monitor_device]
        if self.var_id == "network_tx":
            return round_nicely(data["tx"] / 1024 / 1024)

        if self.var_id == "network_rx":
            return round_nicely(data["rx"] / 1024 / 1024)

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        if self._api.data:
            data = self._api.data["system_stats"]["nics"][self.monitor_device]
            return {
                ATTR_IP: data["ip"],
                ATTR_MASK: data["mask"],
                ATTR_MAC: data["mac"],
                ATTR_MAX_SPEED: data["max_speed"],
                ATTR_PACKETS_TX: data["tx_packets"],
                ATTR_PACKETS_RX: data["rx_packets"],
                ATTR_PACKETS_ERR: data["err_packets"]
            }


class QNAPSystemSensor(QNAPSensor):
    """A QNAP sensor that monitors overall system health."""

    @property
    def state(self):
        """Return the state of the sensor."""
        if self.var_id == "status":
            return self._api.data["system_health"]

        if self.var_id == "system_temp":
            return int(self._api.data["system_stats"]["system"]["temp_c"])

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        if self._api.data:
            data = self._api.data["system_stats"]
            days = int(data["uptime"]["days"])
            hours = int(data["uptime"]["hours"])
            minutes = int(data["uptime"]["minutes"])

            return {
                ATTR_NAME: data["system"]["name"],
                ATTR_MODEL: data["system"]["model"],
                ATTR_SERIAL: data["system"]["serial_number"],
                ATTR_UPTIME: "{:0>2d}d {:0>2d}h {:0>2d}m".format(days,
                                                                 hours,
                                                                 minutes)
            }


class QNAPDriveSensor(QNAPSensor):
    """A QNAP sensor that monitors HDD/SSD drive stats."""

    @property
    def state(self):
        """Return the state of the sensor."""
        data = self._api.data["smart_drive_health"][self.monitor_device]

        if self.var_id == "drive_smart_status":
            return data["health"]

        if self.var_id == "drive_temp":
            return int(data["temp_c"])

    @property
    def name(self):
        """Return the name of the sensor, if any."""
        server_name = self._api.data["system_stats"]["system"]["name"]

        return "{} {} (Drive {})".format(
            server_name,
            self.var_name,
            self.monitor_device
        )

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        if self._api.data:
            data = self._api.data["smart_drive_health"][self.monitor_device]
            return {
                ATTR_DRIVE: data["drive_number"],
                ATTR_MODEL: data["model"],
                ATTR_SERIAL: data["serial"],
                ATTR_TYPE: data["type"],
            }


class QNAPVolumeSensor(QNAPSensor):
    """A QNAP sensor that monitors storage volume stats."""

    @property
    def state(self):
        """Return the state of the sensor."""
        data = self._api.data["volumes"][self.monitor_device]

        free_gb = int(data["free_size"]) / 1024 / 1024 / 1024
        if self.var_id == "volume_size_free":
            return round_nicely(free_gb)

        total_gb = int(data["total_size"]) / 1024 / 1024 / 1024

        used_gb = total_gb - free_gb
        if self.var_id == "volume_size_used":
            return round_nicely(used_gb)

        if self.var_id == "volume_percentage_used":
            return round(used_gb / total_gb * 100)

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        if self._api.data:
            data = self._api.data["volumes"][self.monitor_device]
            total_gb = int(data["total_size"]) / 1024 / 1024 / 1024

            return {
                ATTR_VOLUME_SIZE: "{} GB".format(round_nicely(total_gb)),
            }
