"""
Support for Dialogflow webhook.

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

import voluptuous as vol

from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, template
from homeassistant.components.http import HomeAssistantView

_LOGGER = logging.getLogger(__name__)

CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
CONF_ASYNC_ACTION = 'async_action'

DEFAULT_CONF_ASYNC_ACTION = False
DEPENDENCIES = ['http']
DOMAIN = 'dialogflow'

INTENTS_API_ENDPOINT = '/api/dialogflow'

SOURCE = "Home Assistant Dialogflow"

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: {}
}, extra=vol.ALLOW_EXTRA)


class DialogFlowError(HomeAssistantError):
    """Raised when a DialogFlow error happens."""


@asyncio.coroutine
def async_setup(hass, config):
    """Set up Dialogflow component."""
    hass.http.register_view(DialogflowIntentsView)

    return True


class DialogflowIntentsView(HomeAssistantView):
    """Handle Dialogflow requests."""

    url = INTENTS_API_ENDPOINT
    name = 'api:dialogflow'

    @asyncio.coroutine
    def post(self, request):
        """Handle Dialogflow."""
        hass = request.app['hass']
        message = yield from request.json()

        _LOGGER.debug("Received Dialogflow request: %s", message)

        try:
            response = yield from async_handle_message(hass, message)
            return b'' if response is None else self.json(response)

        except DialogFlowError as err:
            _LOGGER.warning(str(err))
            return self.json(dialogflow_error_response(
                hass, message, str(err)))

        except intent.UnknownIntent as err:
            _LOGGER.warning(str(err))
            return self.json(dialogflow_error_response(
                hass, message,
                "This intent is not yet configured within Home Assistant."))

        except intent.InvalidSlotInfo as err:
            _LOGGER.warning(str(err))
            return self.json(dialogflow_error_response(
                hass, message,
                "Invalid slot information received for this intent."))

        except intent.IntentError as err:
            _LOGGER.warning(str(err))
            return self.json(dialogflow_error_response(
                hass, message, "Error handling intent."))


def dialogflow_error_response(hass, message, error):
    """Return a response saying the error message."""
    dialogflow_response = DialogflowResponse(message['result']['parameters'])
    dialogflow_response.add_speech(error)
    return dialogflow_response.as_dict()


@asyncio.coroutine
def async_handle_message(hass, message):
    """Handle a DialogFlow message."""
    req = message.get('result')
    action_incomplete = req['actionIncomplete']

    if action_incomplete:
        return None

    action = req.get('action', '')
    parameters = req.get('parameters')
    dialogflow_response = DialogflowResponse(parameters)

    if action == "":
        raise DialogFlowError(
            "You have not defined an action in your Dialogflow intent.")

    intent_response = yield from intent.async_handle(
        hass, DOMAIN, action,
        {key: {'value': value} for key, value
         in parameters.items()})

    if 'plain' in intent_response.speech:
        dialogflow_response.add_speech(
            intent_response.speech['plain']['speech'])

    return dialogflow_response.as_dict()


class DialogflowResponse(object):
    """Help generating the response for Dialogflow."""

    def __init__(self, parameters):
        """Initialize the Dialogflow response."""
        self.speech = None
        self.parameters = {}
        # Parameter names replace '.' and '-' for '_'
        for key, value in parameters.items():
            underscored_key = key.replace('.', '_').replace('-', '_')
            self.parameters[underscored_key] = value

    def add_speech(self, text):
        """Add speech to the response."""
        assert self.speech is None

        if isinstance(text, template.Template):
            text = text.async_render(self.parameters)

        self.speech = text

    def as_dict(self):
        """Return response in a Dialogflow valid dictionary."""
        return {
            'speech': self.speech,
            'displayText': self.speech,
            'source': SOURCE,
        }
