"""
Support for Telldus Live.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/tellduslive/
"""
from datetime import datetime, timedelta
import logging

import voluptuous as vol

from homeassistant.const import (
    ATTR_BATTERY_LEVEL, DEVICE_DEFAULT_NAME,
    CONF_TOKEN, CONF_HOST,
    EVENT_HOMEASSISTANT_START)
from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_TELLDUSLIVE
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util.json import load_json, save_json

APPLICATION_NAME = 'Home Assistant'

DOMAIN = 'tellduslive'

REQUIREMENTS = ['tellduslive==0.10.4']

_LOGGER = logging.getLogger(__name__)

TELLLDUS_CONFIG_FILE = 'tellduslive.conf'
KEY_CONFIG = 'tellduslive_config'

CONF_TOKEN_SECRET = 'token_secret'
CONF_UPDATE_INTERVAL = 'update_interval'

PUBLIC_KEY = 'THUPUNECH5YEQA3RE6UYUPRUZ2DUGUGA'
NOT_SO_PRIVATE_KEY = 'PHES7U2RADREWAFEBUSTUBAWRASWUTUS'

MIN_UPDATE_INTERVAL = timedelta(seconds=5)
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=1)

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_HOST): cv.string,
        vol.Optional(CONF_UPDATE_INTERVAL, default=DEFAULT_UPDATE_INTERVAL): (
            vol.All(cv.time_period, vol.Clamp(min=MIN_UPDATE_INTERVAL)))
    }),
}, extra=vol.ALLOW_EXTRA)


ATTR_LAST_UPDATED = 'time_last_updated'

CONFIG_INSTRUCTIONS = """
To link your TelldusLive account:

1. Click the link below

2. Login to Telldus Live

3. Authorize {app_name}.

4. Click the Confirm button.

[Link TelldusLive account]({auth_url})
"""


def setup(hass, config, session=None):
    """Set up the Telldus Live component."""
    from tellduslive import Session, supports_local_api
    config_filename = hass.config.path(TELLLDUS_CONFIG_FILE)
    conf = load_json(config_filename)

    def request_configuration(host=None):
        """Request TelldusLive authorization."""
        configurator = hass.components.configurator
        hass.data.setdefault(KEY_CONFIG, {})
        data_key = host or DOMAIN

        # Configuration already in progress
        if hass.data[KEY_CONFIG].get(data_key):
            return

        _LOGGER.info('Configuring TelldusLive %s',
                     'local client: {}'.format(host) if host else
                     'cloud service')

        session = Session(public_key=PUBLIC_KEY,
                          private_key=NOT_SO_PRIVATE_KEY,
                          host=host,
                          application=APPLICATION_NAME)

        auth_url = session.authorize_url
        if not auth_url:
            _LOGGER.warning('Failed to retrieve authorization URL')
            return

        _LOGGER.debug('Got authorization URL %s', auth_url)

        def configuration_callback(callback_data):
            """Handle the submitted configuration."""
            session.authorize()
            res = setup(hass, config, session)
            if not res:
                configurator.notify_errors(
                    hass.data[KEY_CONFIG].get(data_key),
                    'Unable to connect.')
                return

            conf.update(
                {host: {CONF_HOST: host,
                        CONF_TOKEN: session.access_token}} if host else
                {DOMAIN: {CONF_TOKEN: session.access_token,
                          CONF_TOKEN_SECRET: session.access_token_secret}})
            save_json(config_filename, conf)
            # Close all open configurators: for now, we only support one
            # tellstick device, and configuration via either cloud service
            # or via local API, not both at the same time
            for instance in hass.data[KEY_CONFIG].values():
                configurator.request_done(instance)

        hass.data[KEY_CONFIG][data_key] = \
            configurator.request_config(
                'TelldusLive ({})'.format(
                    'LocalAPI' if host
                    else 'Cloud service'),
                configuration_callback,
                description=CONFIG_INSTRUCTIONS.format(
                    app_name=APPLICATION_NAME,
                    auth_url=auth_url),
                submit_caption='Confirm',
                entity_picture='/static/images/logo_tellduslive.png',
            )

    def tellstick_discovered(service, info):
        """Run when a Tellstick is discovered."""
        _LOGGER.info('Discovered tellstick device')

        if DOMAIN in hass.data:
            _LOGGER.debug('Tellstick already configured')
            return

        host, device = info[:2]

        if not supports_local_api(device):
            _LOGGER.debug('Tellstick does not support local API')
            # Configure the cloud service
            hass.async_add_job(request_configuration)
            return

        _LOGGER.debug('Tellstick does support local API')

        # Ignore any known devices
        if conf and host in conf:
            _LOGGER.debug('Discovered already known device: %s', host)
            return

        # Offer configuration of both live and local API
        request_configuration()
        request_configuration(host)

    discovery.listen(hass, SERVICE_TELLDUSLIVE, tellstick_discovered)

    if session:
        _LOGGER.debug('Continuing setup configured by configurator')
    elif conf and CONF_HOST in next(iter(conf.values())):
        #  For now, only one local device is supported
        _LOGGER.debug('Using Local API pre-configured by configurator')
        session = Session(**next(iter(conf.values())))
    elif DOMAIN in conf:
        _LOGGER.debug('Using TelldusLive cloud service '
                      'pre-configured by configurator')
        session = Session(PUBLIC_KEY, NOT_SO_PRIVATE_KEY,
                          application=APPLICATION_NAME, **conf[DOMAIN])
    elif config.get(DOMAIN):
        _LOGGER.info('Found entry in configuration.yaml. '
                     'Requesting TelldusLive cloud service configuration')
        request_configuration()

        if CONF_HOST in config.get(DOMAIN, {}):
            _LOGGER.info('Found TelldusLive host entry in configuration.yaml. '
                         'Requesting Telldus Local API configuration')
            request_configuration(config.get(DOMAIN).get(CONF_HOST))

        return True
    else:
        _LOGGER.info('Tellstick discovered, awaiting discovery callback')
        return True

    if not session.is_authorized:
        _LOGGER.error(
            'Authentication Error')
        return False

    client = TelldusLiveClient(hass, config, session)

    hass.data[DOMAIN] = client

    if session:
        client.update()
    else:
        hass.bus.listen(EVENT_HOMEASSISTANT_START, client.update)

    return True


class TelldusLiveClient:
    """Get the latest data and update the states."""

    def __init__(self, hass, config, session):
        """Initialize the Tellus data object."""
        self.entities = []

        self._hass = hass
        self._config = config

        self._interval = config.get(DOMAIN, {}).get(
            CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL)
        _LOGGER.debug('Update interval %s', self._interval)
        self._client = session

    def update(self, *args):
        """Periodically poll the servers for current state."""
        _LOGGER.debug('Updating')
        try:
            self._sync()
        finally:
            track_point_in_utc_time(
                self._hass, self.update, utcnow() + self._interval)

    def _sync(self):
        """Update local list of devices."""
        if not self._client.update():
            _LOGGER.warning('Failed request')

        def identify_device(device):
            """Find out what type of HA component to create."""
            from tellduslive import (DIM, UP, TURNON)
            if device.methods & DIM:
                return 'light'
            if device.methods & UP:
                return 'cover'
            if device.methods & TURNON:
                return 'switch'
            if device.methods == 0:
                return 'binary_sensor'
            _LOGGER.warning(
                "Unidentified device type (methods: %d)", device.methods)
            return 'switch'

        def discover(device_id, component):
            """Discover the component."""
            discovery.load_platform(
                self._hass, component, DOMAIN, [device_id], self._config)

        known_ids = {entity.device_id for entity in self.entities}
        for device in self._client.devices:
            if device.device_id in known_ids:
                continue
            if device.is_sensor:
                for item in device.items:
                    discover((device.device_id, item.name, item.scale),
                             'sensor')
            else:
                discover(device.device_id,
                         identify_device(device))

        for entity in self.entities:
            entity.changed()

    def device(self, device_id):
        """Return device representation."""
        return self._client.device(device_id)

    def is_available(self, device_id):
        """Return device availability."""
        return device_id in self._client.device_ids


class TelldusLiveEntity(Entity):
    """Base class for all Telldus Live entities."""

    def __init__(self, hass, device_id):
        """Initialize the entity."""
        self._id = device_id
        self._client = hass.data[DOMAIN]
        self._client.entities.append(self)
        self._name = self.device.name
        _LOGGER.debug('Created device %s', self)

    def changed(self):
        """Return the property of the device might have changed."""
        if self.device.name:
            self._name = self.device.name
        self.schedule_update_ha_state()

    @property
    def device_id(self):
        """Return the id of the device."""
        return self._id

    @property
    def device(self):
        """Return the representation of the device."""
        return self._client.device(self.device_id)

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

    @property
    def should_poll(self):
        """Return the polling state."""
        return False

    @property
    def assumed_state(self):
        """Return true if unable to access real state of entity."""
        return True

    @property
    def name(self):
        """Return name of device."""
        return self._name or DEVICE_DEFAULT_NAME

    @property
    def available(self):
        """Return true if device is not offline."""
        return self._client.is_available(self.device_id)

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        attrs = {}
        if self._battery_level:
            attrs[ATTR_BATTERY_LEVEL] = self._battery_level
        if self._last_updated:
            attrs[ATTR_LAST_UPDATED] = self._last_updated
        return attrs

    @property
    def _battery_level(self):
        """Return the battery level of a device."""
        from tellduslive import (BATTERY_LOW,
                                 BATTERY_UNKNOWN,
                                 BATTERY_OK)
        if self.device.battery == BATTERY_LOW:
            return 1
        if self.device.battery == BATTERY_UNKNOWN:
            return None
        if self.device.battery == BATTERY_OK:
            return 100
        return self.device.battery  # Percentage

    @property
    def _last_updated(self):
        """Return the last update of a device."""
        return str(datetime.fromtimestamp(self.device.lastUpdated)) \
            if self.device.lastUpdated else None
