"""Support for Bbox Bouygues Modem Router."""
from datetime import timedelta
import logging

import pybbox
import requests
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    ATTR_ATTRIBUTION,
    CONF_MONITORED_VARIABLES,
    CONF_NAME,
    DATA_RATE_MEGABITS_PER_SECOND,
    DEVICE_CLASS_TIMESTAMP,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from homeassistant.util.dt import utcnow

_LOGGER = logging.getLogger(__name__)

ATTRIBUTION = "Powered by Bouygues Telecom"

DEFAULT_NAME = "Bbox"

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)

# Sensor types are defined like so: Name, unit, icon
SENSOR_TYPES = {
    "down_max_bandwidth": [
        "Maximum Download Bandwidth",
        DATA_RATE_MEGABITS_PER_SECOND,
        "mdi:download",
    ],
    "up_max_bandwidth": [
        "Maximum Upload Bandwidth",
        DATA_RATE_MEGABITS_PER_SECOND,
        "mdi:upload",
    ],
    "current_down_bandwidth": [
        "Currently Used Download Bandwidth",
        DATA_RATE_MEGABITS_PER_SECOND,
        "mdi:download",
    ],
    "current_up_bandwidth": [
        "Currently Used Upload Bandwidth",
        DATA_RATE_MEGABITS_PER_SECOND,
        "mdi:upload",
    ],
    "uptime": ["Uptime", None, "mdi:clock"],
    "number_of_reboots": ["Number of reboot", None, "mdi:restart"],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_MONITORED_VARIABLES): vol.All(
            cv.ensure_list, [vol.In(SENSOR_TYPES)]
        ),
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    }
)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Bbox sensor."""
    # Create a data fetcher to support all of the configured sensors. Then make
    # the first call to init the data.
    try:
        bbox_data = BboxData()
        bbox_data.update()
    except requests.exceptions.HTTPError as error:
        _LOGGER.error(error)
        return False

    name = config.get(CONF_NAME)

    sensors = []
    for variable in config[CONF_MONITORED_VARIABLES]:
        if variable == "uptime":
            sensors.append(BboxUptimeSensor(bbox_data, variable, name))
        else:
            sensors.append(BboxSensor(bbox_data, variable, name))

    add_entities(sensors, True)


class BboxUptimeSensor(Entity):
    """Bbox uptime sensor."""

    def __init__(self, bbox_data, sensor_type, name):
        """Initialize the sensor."""
        self.client_name = name
        self.type = sensor_type
        self._name = SENSOR_TYPES[sensor_type][0]
        self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
        self._icon = SENSOR_TYPES[sensor_type][2]
        self.bbox_data = bbox_data
        self._state = None

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

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

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

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

    @property
    def device_class(self):
        """Return the class of this sensor."""
        return DEVICE_CLASS_TIMESTAMP

    def update(self):
        """Get the latest data from Bbox and update the state."""
        self.bbox_data.update()
        uptime = utcnow() - timedelta(
            seconds=self.bbox_data.router_infos["device"]["uptime"]
        )
        self._state = uptime.replace(microsecond=0).isoformat()


class BboxSensor(Entity):
    """Implementation of a Bbox sensor."""

    def __init__(self, bbox_data, sensor_type, name):
        """Initialize the sensor."""
        self.client_name = name
        self.type = sensor_type
        self._name = SENSOR_TYPES[sensor_type][0]
        self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
        self._icon = SENSOR_TYPES[sensor_type][2]
        self.bbox_data = bbox_data
        self._state = None

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

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

    @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 self._icon

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

    def update(self):
        """Get the latest data from Bbox and update the state."""
        self.bbox_data.update()
        if self.type == "down_max_bandwidth":
            self._state = round(self.bbox_data.data["rx"]["maxBandwidth"] / 1000, 2)
        elif self.type == "up_max_bandwidth":
            self._state = round(self.bbox_data.data["tx"]["maxBandwidth"] / 1000, 2)
        elif self.type == "current_down_bandwidth":
            self._state = round(self.bbox_data.data["rx"]["bandwidth"] / 1000, 2)
        elif self.type == "current_up_bandwidth":
            self._state = round(self.bbox_data.data["tx"]["bandwidth"] / 1000, 2)
        elif self.type == "number_of_reboots":
            self._state = self.bbox_data.router_infos["device"]["numberofboots"]


class BboxData:
    """Get data from the Bbox."""

    def __init__(self):
        """Initialize the data object."""
        self.data = None
        self.router_infos = None

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Get the latest data from the Bbox."""

        try:
            box = pybbox.Bbox()
            self.data = box.get_ip_stats()
            self.router_infos = box.get_bbox_info()
        except requests.exceptions.HTTPError as error:
            _LOGGER.error(error)
            self.data = None
            self.router_infos = None
            return False
