"""The HTTP api to control the cloud integration."""
import asyncio
from functools import wraps
import logging

import aiohttp
import async_timeout
import voluptuous as vol

from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import (
    RequestDataValidator)
from homeassistant.components import websocket_api
from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import smart_home as google_sh

from . import auth_api
from .const import (
    DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
    PREF_GOOGLE_ALLOW_UNLOCK)
from .iot import STATE_DISCONNECTED, STATE_CONNECTED

_LOGGER = logging.getLogger(__name__)


WS_TYPE_STATUS = 'cloud/status'
SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
    vol.Required('type'): WS_TYPE_STATUS,
})


WS_TYPE_UPDATE_PREFS = 'cloud/update_prefs'
SCHEMA_WS_UPDATE_PREFS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
    vol.Required('type'): WS_TYPE_UPDATE_PREFS,
    vol.Optional(PREF_ENABLE_GOOGLE): bool,
    vol.Optional(PREF_ENABLE_ALEXA): bool,
    vol.Optional(PREF_GOOGLE_ALLOW_UNLOCK): bool,
})


WS_TYPE_SUBSCRIPTION = 'cloud/subscription'
SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
    vol.Required('type'): WS_TYPE_SUBSCRIPTION,
})


WS_TYPE_HOOK_CREATE = 'cloud/cloudhook/create'
SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
    vol.Required('type'): WS_TYPE_HOOK_CREATE,
    vol.Required('webhook_id'): str
})


WS_TYPE_HOOK_DELETE = 'cloud/cloudhook/delete'
SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
    vol.Required('type'): WS_TYPE_HOOK_DELETE,
    vol.Required('webhook_id'): str
})


async def async_setup(hass):
    """Initialize the HTTP API."""
    hass.components.websocket_api.async_register_command(
        WS_TYPE_STATUS, websocket_cloud_status,
        SCHEMA_WS_STATUS
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_SUBSCRIPTION, websocket_subscription,
        SCHEMA_WS_SUBSCRIPTION
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_UPDATE_PREFS, websocket_update_prefs,
        SCHEMA_WS_UPDATE_PREFS
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_HOOK_CREATE, websocket_hook_create,
        SCHEMA_WS_HOOK_CREATE
    )
    hass.components.websocket_api.async_register_command(
        WS_TYPE_HOOK_DELETE, websocket_hook_delete,
        SCHEMA_WS_HOOK_DELETE
    )
    hass.http.register_view(GoogleActionsSyncView)
    hass.http.register_view(CloudLoginView)
    hass.http.register_view(CloudLogoutView)
    hass.http.register_view(CloudRegisterView)
    hass.http.register_view(CloudResendConfirmView)
    hass.http.register_view(CloudForgotPasswordView)


_CLOUD_ERRORS = {
    auth_api.UserNotFound: (400, "User does not exist."),
    auth_api.UserNotConfirmed: (400, 'Email not confirmed.'),
    auth_api.Unauthenticated: (401, 'Authentication failed.'),
    auth_api.PasswordChangeRequired: (400, 'Password change required.'),
    asyncio.TimeoutError: (502, 'Unable to reach the Home Assistant cloud.')
}


def _handle_cloud_errors(handler):
    """Webview decorator to handle auth errors."""
    @wraps(handler)
    async def error_handler(view, request, *args, **kwargs):
        """Handle exceptions that raise from the wrapped request handler."""
        try:
            result = await handler(view, request, *args, **kwargs)
            return result

        except (auth_api.CloudError, asyncio.TimeoutError) as err:
            err_info = _CLOUD_ERRORS.get(err.__class__)
            if err_info is None:
                err_info = (502, 'Unexpected error: {}'.format(err))
            status, msg = err_info
            return view.json_message(msg, status_code=status,
                                     message_code=err.__class__.__name__)

    return error_handler


class GoogleActionsSyncView(HomeAssistantView):
    """Trigger a Google Actions Smart Home Sync."""

    url = '/api/cloud/google_actions/sync'
    name = 'api:cloud:google_actions/sync'

    @_handle_cloud_errors
    async def post(self, request):
        """Trigger a Google Actions sync."""
        hass = request.app['hass']
        cloud = hass.data[DOMAIN]
        websession = hass.helpers.aiohttp_client.async_get_clientsession()

        with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
            await hass.async_add_job(auth_api.check_token, cloud)

        with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
            req = await websession.post(
                cloud.google_actions_sync_url, headers={
                    'authorization': cloud.id_token
                })

        return self.json({}, status_code=req.status)


class CloudLoginView(HomeAssistantView):
    """Login to Home Assistant cloud."""

    url = '/api/cloud/login'
    name = 'api:cloud:login'

    @_handle_cloud_errors
    @RequestDataValidator(vol.Schema({
        vol.Required('email'): str,
        vol.Required('password'): str,
    }))
    async def post(self, request, data):
        """Handle login request."""
        hass = request.app['hass']
        cloud = hass.data[DOMAIN]

        with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
            await hass.async_add_job(auth_api.login, cloud, data['email'],
                                     data['password'])

        hass.async_add_job(cloud.iot.connect)
        return self.json({'success': True})


class CloudLogoutView(HomeAssistantView):
    """Log out of the Home Assistant cloud."""

    url = '/api/cloud/logout'
    name = 'api:cloud:logout'

    @_handle_cloud_errors
    async def post(self, request):
        """Handle logout request."""
        hass = request.app['hass']
        cloud = hass.data[DOMAIN]

        with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):