"""Support for Smappee energy monitor."""
from datetime import datetime, timedelta
import logging
import re

from requests.exceptions import RequestException
import smappy
import voluptuous as vol

from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle

_LOGGER = logging.getLogger(__name__)

DEFAULT_NAME = "Smappee"
DEFAULT_HOST_PASSWORD = "admin"

CONF_CLIENT_ID = "client_id"
CONF_CLIENT_SECRET = "client_secret"
CONF_HOST_PASSWORD = "host_password"

DOMAIN = "smappee"
DATA_SMAPPEE = "SMAPPEE"

_SENSOR_REGEX = re.compile(r"(?P<key>([A-Za-z]+))\=" + r"(?P<value>([0-9\.]+))")

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {
                vol.Inclusive(CONF_CLIENT_ID, "Server credentials"): cv.string,
                vol.Inclusive(CONF_CLIENT_SECRET, "Server credentials"): cv.string,
                vol.Inclusive(CONF_USERNAME, "Server credentials"): cv.string,
                vol.Inclusive(CONF_PASSWORD, "Server credentials"): cv.string,
                vol.Optional(CONF_HOST): cv.string,
                vol.Optional(
                    CONF_HOST_PASSWORD, default=DEFAULT_HOST_PASSWORD
                ): cv.string,
            }
        )
    },
    extra=vol.ALLOW_EXTRA,
)

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)


def setup(hass, config):
    """Set up the Smapee component."""
    client_id = config.get(DOMAIN).get(CONF_CLIENT_ID)
    client_secret = config.get(DOMAIN).get(CONF_CLIENT_SECRET)
    username = config.get(DOMAIN).get(CONF_USERNAME)
    password = config.get(DOMAIN).get(CONF_PASSWORD)
    host = config.get(DOMAIN).get(CONF_HOST)
    host_password = config.get(DOMAIN).get(CONF_HOST_PASSWORD)

    smappee = Smappee(client_id, client_secret, username, password, host, host_password)

    if not smappee.is_local_active and not smappee.is_remote_active:
        _LOGGER.error("Neither Smappee server or local integration enabled.")
        return False

    hass.data[DATA_SMAPPEE] = smappee
    load_platform(hass, "switch", DOMAIN, {}, config)
    load_platform(hass, "sensor", DOMAIN, {}, config)
    return True


class Smappee:
    """Stores data retrieved from Smappee sensor."""

    def __init__(
        self, client_id, client_secret, username, password, host, host_password
    ):
        """Initialize the data."""

        self._remote_active = False
        self._local_active = False
        if client_id is not None:
            try:
                self._smappy = smappy.Smappee(client_id, client_secret)
                self._smappy.authenticate(username, password)
                self._remote_active = True
            except RequestException as error:
                self._smappy = None
                _LOGGER.exception("Smappee server authentication failed (%s)", error)
        else:
            _LOGGER.warning("Smappee server integration init skipped.")

        if host is not None:
            try:
                self._localsmappy = smappy.LocalSmappee(host)
                self._localsmappy.logon(host_password)
                self._local_active = True
            except RequestException as error:
                self._localsmappy = None
                _LOGGER.exception(
                    "Local Smappee device authentication failed (%s)", error
                )
        else:
            _LOGGER.warning("Smappee local integration init skipped.")

        self.locations = {}
        self.info = {}
        self.consumption = {}
        self.sensor_consumption = {}
        self.instantaneous = {}

        if self._remote_active or self._local_active:
            self.update()

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        """Update data from Smappee API."""
        if self.is_remote_active:
            service_locations = self._smappy.get_service_locations().get(
                "serviceLocations"
            )
            for location in service_locations:
                location_id = location.get("serviceLocationId")
                if location_id is not None:
                    self.sensor_consumption[location_id] = {}
                    self.locations[location_id] = location.get("name")
                    self.info[location_id] = self._smappy.get_service_location_info(
                        location_id
                    )
                    _LOGGER.debug(
                        "Remote info %s %s", self.locations, self.info[location_id]
                    )

                    for sensors in self.info[location_id].get("sensors"):
                        sensor_id = sensors.get("id")
                        self.sensor_consumption[location_id].update(
                            {
                                sensor_id: self.get_sensor_consumption(
                                    location_id, sensor_id, aggregation=3, delta=1440
                                )
                            }
                        )
                    _LOGGER.debug(
                        "Remote sensors %s %s",
                        self.locations,
                        self.sensor_consumption[location_id],
                    )

                    self.consumption[location_id] = self.get_consumption(
                        location_id, aggregation=3, delta=1440
                    )
                    _LOGGER.debug(
                        "Remote consumption %s %s",
                        self.locations,
                        self.consumption[location_id],
                    )

        if self.is_local_active:
            self.local_devices = self.get_switches()
            _LOGGER.debug("Local switches %s", self.local_devices)

            self.instantaneous = self.load_instantaneous()
            _LOGGER.debug("Local values %s", self.instantaneous)

    @property
    def is_remote_active(self):
        """Return true if Smappe server is configured and working."""
        return self._remote_active

    @property
    def is_local_active(self):
        """Return true if Smappe local device is configured and working."""
        return self._local_active

    def get_switches(self):
        """Get switches from local Smappee."""
        if not self.is_local_active:
            return

        try:
            return self._localsmappy.load_command_control_config()
        except RequestException as error:
            _LOGGER.error("Error getting switches from local Smappee. (%s)", error)

    def get_consumption(self, location_id, aggregation, delta):
        """Update data from Smappee."""
        # Start & End accept epoch (in milliseconds),
        #   datetime and pandas timestamps
        # Aggregation:
        # 1 = 5 min values (only available for the last 14 days),
        # 2 = hourly values,
        # 3 = daily values,
        # 4 = monthly values,
        # 5 = quarterly values
        if not self.is_remote_active:
            return

        end = datetime.utcnow()
        start = end - timedelta(minutes=delta)
        try:
            return self._smappy.get_consumption(location_id, start, end, aggregation)
        except RequestException as error:
            _LOGGER.error("Error getting comsumption from Smappee cloud. (%s)", error)

    def get_sensor_consumption(self, location_id, sensor_id, aggregation, delta):
        """Update data from Smappee."""
        # Start & End accept epoch (in milliseconds),
        #   datetime and pandas timestamps
        # Aggregation:
        # 1 = 5 min values (only available for the last 14 days),
        # 2 = hourly values,
        # 3 = daily values,
        # 4 = monthly values,
        # 5 = quarterly values
        if not self.is_remote_active:
            return

        end = datetime.utcnow()
        start = end - timedelta(minutes=delta)
        try:
            return self._smappy.get_sensor_consumption(
                location_id, sensor_id, start, end, aggregation
            )
        except RequestException as error:
            _LOGGER.error("Error getting comsumption from Smappee cloud. (%s)", error)

    def actuator_on(self, location_id, actuator_id, is_remote_switch, duration=None):
        """Turn on actuator."""
        # Duration = 300,900,1800,3600
        #  or any other value for an undetermined period of time.
        #
        # The comport plugs have a tendency to ignore the on/off signal.
        # And because you can't read the status of a plug, it's more
        # reliable to execute the command twice.
        try:
            if is_remote_switch:
                self._smappy.actuator_on(location_id, actuator_id, duration)
                self._smappy.actuator_on(location_id, actuator_id, duration)
            else:
                self._localsmappy.on_command_control(actuator_id)
                self._localsmappy.on_command_control(actuator_id)
        except RequestException as error:
            _LOGGER.error("Error turning actuator on. (%s)", error)
            return False

        return True

    def actuator_off(self, location_id, actuator_id, is_remote_switch, duration=None):
        """Turn off actuator."""
        # Duration = 300,900,1800,3600
        #  or any other value for an undetermined period of time.
        #
        # The comport plugs have a tendency to ignore the on/off signal.
        # And because you can't read the status of a plug, it's more
        # reliable to execute the command twice.
        try:
            if is_remote_switch:
                self._smappy.actuator_off(location_id, actuator_id, duration)
                self._smappy.actuator_off(location_id, actuator_id, duration)
            else:
                self._localsmappy.off_command_control(actuator_id)
                self._localsmappy.off_command_control(actuator_id)
        except RequestException as error:
            _LOGGER.error("Error turning actuator on. (%s)", error)
            return False

        return True

    def active_power(self):
        """Get sum of all instantaneous active power values from local hub."""
        if not self.is_local_active:
            return

        try:
            return self._localsmappy.active_power()
        except RequestException as error:
            _LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)

    def active_cosfi(self):
        """Get the average of all instantaneous cosfi values."""
        if not self.is_local_active:
            return

        try:
            return self._localsmappy.active_cosfi()
        except RequestException as error:
            _LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)

    def instantaneous_values(self):
        """ReportInstantaneousValues."""
        if not self.is_local_active:
            return

        report_instantaneous_values = self._localsmappy.report_instantaneous_values()

        report_result = report_instantaneous_values["report"].split("<BR>")
        properties = {}
        for lines in report_result:
            lines_result = lines.split(",")
            for prop in lines_result:
                match = _SENSOR_REGEX.search(prop)
                if match:
                    properties[match.group("key")] = match.group("value")
        _LOGGER.debug(properties)
        return properties

    def active_current(self):
        """Get current active Amps."""
        if not self.is_local_active:
            return

        properties = self.instantaneous_values()
        return float(properties["current"])

    def active_voltage(self):
        """Get current active Voltage."""
        if not self.is_local_active:
            return

        properties = self.instantaneous_values()
        return float(properties["voltage"])

    def load_instantaneous(self):
        """LoadInstantaneous."""
        if not self.is_local_active:
            return

        try:
            return self._localsmappy.load_instantaneous()
        except RequestException as error:
            _LOGGER.error("Error getting data from Local Smappee unit. (%s)", error)
