"""Component to integrate the Home Assistant cloud."""
import logging

import voluptuous as vol

from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components.alexa import const as alexa_const
from homeassistant.components.google_assistant import const as ga_c
from homeassistant.const import (
    CONF_MODE, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START,
    EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entityfilter
from homeassistant.loader import bind_hass
from homeassistant.util.aiohttp import MockRequest

from . import http_api
from .const import (
    CONF_ACME_DIRECTORY_SERVER, CONF_ALEXA, CONF_ALIASES,
    CONF_CLOUDHOOK_CREATE_URL, CONF_COGNITO_CLIENT_ID, CONF_ENTITY_CONFIG,
    CONF_FILTER, CONF_GOOGLE_ACTIONS, CONF_GOOGLE_ACTIONS_SYNC_URL,
    CONF_RELAYER, CONF_REMOTE_API_URL, CONF_SUBSCRIPTION_INFO_URL,
    CONF_USER_POOL_ID, DOMAIN, MODE_DEV, MODE_PROD, CONF_ALEXA_ACCESS_TOKEN_URL
)
from .prefs import CloudPreferences

_LOGGER = logging.getLogger(__name__)

DEFAULT_MODE = MODE_PROD

SERVICE_REMOTE_CONNECT = 'remote_connect'
SERVICE_REMOTE_DISCONNECT = 'remote_disconnect'


ALEXA_ENTITY_SCHEMA = vol.Schema({
    vol.Optional(alexa_const.CONF_DESCRIPTION): cv.string,
    vol.Optional(alexa_const.CONF_DISPLAY_CATEGORIES): cv.string,
    vol.Optional(CONF_NAME): cv.string,
})

GOOGLE_ENTITY_SCHEMA = vol.Schema({
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]),
    vol.Optional(ga_c.CONF_ROOM_HINT): cv.string,
})

ASSISTANT_SCHEMA = vol.Schema({
    vol.Optional(CONF_FILTER, default=dict): entityfilter.FILTER_SCHEMA,
})

ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({
    vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})

GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend({
    vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA},
})

# pylint: disable=no-value-for-parameter
CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_MODE, default=DEFAULT_MODE):
            vol.In([MODE_DEV, MODE_PROD]),
        vol.Optional(CONF_COGNITO_CLIENT_ID): str,
        vol.Optional(CONF_USER_POOL_ID): str,
        vol.Optional(CONF_REGION): str,
        vol.Optional(CONF_RELAYER): str,
        vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): vol.Url(),
        vol.Optional(CONF_SUBSCRIPTION_INFO_URL): vol.Url(),
        vol.Optional(CONF_CLOUDHOOK_CREATE_URL): vol.Url(),
        vol.Optional(CONF_REMOTE_API_URL): vol.Url(),
        vol.Optional(CONF_ACME_DIRECTORY_SERVER): vol.Url(),
        vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
        vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
        vol.Optional(CONF_ALEXA_ACCESS_TOKEN_URL): str,
    }),
}, extra=vol.ALLOW_EXTRA)


class CloudNotAvailable(HomeAssistantError):
    """Raised when an action requires the cloud but it's not available."""


@bind_hass
@callback
def async_is_logged_in(hass) -> bool:
    """Test if user is logged in."""
    return DOMAIN in hass.data and hass.data[DOMAIN].is_logged_in


@bind_hass
@callback
def async_active_subscription(hass) -> bool:
    """Test if user has an active subscription."""
    return \
        async_is_logged_in(hass) and not hass.data[DOMAIN].subscription_expired


@bind_hass
async def async_create_cloudhook(hass, webhook_id: str) -> str:
    """Create a cloudhook."""
    if not async_is_logged_in(hass):
        raise CloudNotAvailable

    hook = await hass.data[DOMAIN].cloudhooks.async_create(webhook_id, True)
    return hook['cloudhook_url']


@bind_hass
async def async_delete_cloudhook(hass, webhook_id: str) -> None:
    """Delete a cloudhook."""
    if DOMAIN not in hass.data:
        raise CloudNotAvailable

    await hass.data[DOMAIN].cloudhooks.async_delete(webhook_id)


@bind_hass
@callback
def async_remote_ui_url(hass) -> str:
    """Get the remote UI URL."""
    if not async_is_logged_in(hass):
        raise CloudNotAvailable

    if not hass.data[DOMAIN].remote.instance_domain:
        raise CloudNotAvailable

    return "https://" + hass.data[DOMAIN].remote.instance_domain


def is_cloudhook_request(request):
    """Test if a request came from a cloudhook.

    Async friendly.
    """
    return isinstance(request, MockRequest)


async def async_setup(hass, config):
    """Initialize the Home Assistant cloud."""
    from hass_nabucasa import Cloud
    from .client import CloudClient

    # Process configs
    if DOMAIN in config:
        kwargs = dict(config[DOMAIN])
    else:
        kwargs = {CONF_MODE: DEFAULT_MODE}

    # Alexa/Google custom config
    alexa_conf = kwargs.pop(CONF_ALEXA, None) or ALEXA_SCHEMA({})
    google_conf = kwargs.pop(CONF_GOOGLE_ACTIONS, None) or GACTIONS_SCHEMA({})

    # Cloud settings
    prefs = CloudPreferences(hass)
    await prefs.async_initialize()

    # Cloud user
    if not prefs.cloud_user:
        user = await hass.auth.async_create_system_user(
            'Home Assistant Cloud', [GROUP_ID_ADMIN])
        await prefs.async_update(cloud_user=user.id)

    # Initialize Cloud
    websession = hass.helpers.aiohttp_client.async_get_clientsession()
    client = CloudClient(hass, prefs, websession, alexa_conf, google_conf)
    cloud = hass.data[DOMAIN] = Cloud(client, **kwargs)

    async def _startup(event):
        """Startup event."""
        await cloud.start()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _startup)

    async def _shutdown(event):
        """Shutdown event."""
        await cloud.stop()

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)

    async def _service_handler(service):
        """Handle service for cloud."""
        if service.service == SERVICE_REMOTE_CONNECT:
            await cloud.remote.connect()
            await prefs.async_update(remote_enabled=True)
        elif service.service == SERVICE_REMOTE_DISCONNECT:
            await cloud.remote.disconnect()
            await prefs.async_update(remote_enabled=False)

    hass.helpers.service.async_register_admin_service(
        DOMAIN, SERVICE_REMOTE_CONNECT, _service_handler)
    hass.helpers.service.async_register_admin_service(
        DOMAIN, SERVICE_REMOTE_DISCONNECT, _service_handler)

    loaded_binary_sensor = False

    async def _on_connect():
        """Discover RemoteUI binary sensor."""
        nonlocal loaded_binary_sensor

        if loaded_binary_sensor:
            return

        loaded_binary_sensor = True
        hass.async_create_task(hass.helpers.discovery.async_load_platform(
            'binary_sensor', DOMAIN, {}, config))

    cloud.iot.register_on_connect(_on_connect)

    await http_api.async_setup(hass)
    return True
