"""
Support for Eight smart mattress covers and mattresses.

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

import voluptuous as vol

from homeassistant.core import callback
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
    CONF_USERNAME, CONF_PASSWORD, CONF_SENSORS, CONF_BINARY_SENSORS,
    ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
    async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow

REQUIREMENTS = ['pyeight==0.0.7']

_LOGGER = logging.getLogger(__name__)

CONF_PARTNER = 'partner'

DATA_EIGHT = 'eight_sleep'
DEFAULT_PARTNER = False
DOMAIN = 'eight_sleep'

HEAT_ENTITY = 'heat'
USER_ENTITY = 'user'

HEAT_SCAN_INTERVAL = timedelta(seconds=60)
USER_SCAN_INTERVAL = timedelta(seconds=300)

SIGNAL_UPDATE_HEAT = 'eight_heat_update'
SIGNAL_UPDATE_USER = 'eight_user_update'

NAME_MAP = {
    'left_current_sleep': 'Left Sleep Session',
    'left_last_sleep': 'Left Previous Sleep Session',
    'left_bed_state': 'Left Bed State',
    'left_presence': 'Left Bed Presence',
    'left_bed_temp': 'Left Bed Temperature',
    'left_sleep_stage': 'Left Sleep Stage',
    'right_current_sleep': 'Right Sleep Session',
    'right_last_sleep': 'Right Previous Sleep Session',
    'right_bed_state': 'Right Bed State',
    'right_presence': 'Right Bed Presence',
    'right_bed_temp': 'Right Bed Temperature',
    'right_sleep_stage': 'Right Sleep Stage',
    'room_temp': 'Room Temperature',
}

SENSORS = ['current_sleep',
           'last_sleep',
           'bed_state',
           'bed_temp',
           'sleep_stage']

SERVICE_HEAT_SET = 'heat_set'

ATTR_TARGET_HEAT = 'target'
ATTR_HEAT_DURATION = 'duration'

VALID_TARGET_HEAT = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=100))
VALID_DURATION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=28800))

SERVICE_EIGHT_SCHEMA = vol.Schema({
    ATTR_ENTITY_ID: cv.entity_ids,
    ATTR_TARGET_HEAT: VALID_TARGET_HEAT,
    ATTR_HEAT_DURATION: VALID_DURATION,
    })

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_USERNAME): cv.string,
        vol.Required(CONF_PASSWORD): cv.string,
        vol.Optional(CONF_PARTNER, default=DEFAULT_PARTNER): cv.boolean,
    }),
}, extra=vol.ALLOW_EXTRA)


@asyncio.coroutine
def async_setup(hass, config):
    """Set up the Eight Sleep component."""
    from pyeight.eight import EightSleep

    conf = config.get(DOMAIN)
    user = conf.get(CONF_USERNAME)
    password = conf.get(CONF_PASSWORD)
    partner = conf.get(CONF_PARTNER)

    if hass.config.time_zone is None:
        _LOGGER.error('Timezone is not set in Home Assistant.')
        return False

    timezone = hass.config.time_zone

    eight = EightSleep(user, password, timezone, partner, None, hass.loop)

    hass.data[DATA_EIGHT] = eight

    # Authenticate, build sensors
    success = yield from eight.start()
    if not success:
        # Authentication failed, cannot continue
        return False

    @asyncio.coroutine
    def async_update_heat_data(now):
        """Update heat data from eight in HEAT_SCAN_INTERVAL."""
        yield from eight.update_device_data()
        async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT)

        async_track_point_in_utc_time(
            hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL)

    @asyncio.coroutine
    def async_update_user_data(now):
        """Update user data from eight in USER_SCAN_INTERVAL."""
        yield from eight.update_user_data()
        async_dispatcher_send(hass, SIGNAL_UPDATE_USER)

        async_track_point_in_utc_time(
            hass, async_update_user_data, utcnow() + USER_SCAN_INTERVAL)

    yield from async_update_heat_data(None)
    yield from async_update_user_data(None)

    # Load sub components
    sensors = []
    binary_sensors = []
    if eight.users:
        for user in eight.users:
            obj = eight.users[user]
            for sensor in SENSORS:
                sensors.append('{}_{}'.format(obj.side, sensor))
            binary_sensors.append('{}_presence'.format(obj.side))
        sensors.append('room_temp')
    else:
        # No users, cannot continue
        return False

    hass.async_add_job(discovery.async_load_platform(
        hass, 'sensor', DOMAIN, {
            CONF_SENSORS: sensors,
        }, config))

    hass.async_add_job(discovery.async_load_platform(
        hass, 'binary_sensor', DOMAIN, {
            CONF_BINARY_SENSORS: binary_sensors,
        }, config))

    descriptions = yield from hass.async_add_job(
        load_yaml_config_file,
        os.path.join(os.path.dirname(__file__), 'services.yaml'))

    @asyncio.coroutine
    def async_service_handler(service):
        """Handle eight sleep service calls."""
        params = service.data.copy()

        sensor = params.pop(ATTR_ENTITY_ID, None)
        target = params.pop(ATTR_TARGET_HEAT, None)
        duration = params.pop(ATTR_HEAT_DURATION, 0)

        for sens in sensor:
            side = sens.split('_')[1]
            userid = eight.fetch_userid(side)
            usrobj = eight.users[userid]
            yield from usrobj.set_heating_level(target, duration)

        async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT)

    # Register services
    hass.services.async_register(
        DOMAIN, SERVICE_HEAT_SET, async_service_handler,
        descriptions[DOMAIN].get(SERVICE_HEAT_SET),
        schema=SERVICE_EIGHT_SCHEMA)

    @asyncio.coroutine
    def stop_eight(event):
        """Handle stopping eight api session."""
        yield from eight.stop()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_eight)

    return True


class EightSleepUserEntity(Entity):
    """The Eight Sleep device entity."""

    def __init__(self, eight):
        """Initialize the data oject."""
        self._eight = eight

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register update dispatcher."""
        @callback
        def async_eight_user_update():
            """Update callback."""
            self.async_schedule_update_ha_state(True)

        async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_USER, async_eight_user_update)

    @property
    def should_poll(self):
        """Return True if entity has to be polled for state."""
        return False


class EightSleepHeatEntity(Entity):
    """The Eight Sleep device entity."""

    def __init__(self, eight):
        """Initialize the data oject."""
        self._eight = eight

    @asyncio.coroutine
    def async_added_to_hass(self):
        """Register update dispatcher."""
        @callback
        def async_eight_heat_update():
            """Update callback."""
            self.async_schedule_update_ha_state(True)

        async_dispatcher_connect(
            self.hass, SIGNAL_UPDATE_HEAT, async_eight_heat_update)

    @property
    def should_poll(self):
        """Return True if entity has to be polled for state."""
        return False
