"""The tests for the Conversation component."""
# pylint: disable=protected-access
import pytest

from homeassistant.core import DOMAIN as HASS_DOMAIN
from homeassistant.setup import async_setup_component
from homeassistant.components import conversation
import homeassistant.components as component
from homeassistant.components.cover import (SERVICE_OPEN_COVER)
from homeassistant.helpers import intent

from tests.common import async_mock_intent, async_mock_service


async def test_calling_intent(hass):
    """Test calling an intent from a conversation."""
    intents = async_mock_intent(hass, 'OrderBeer')

    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {
        'conversation': {
            'intents': {
                'OrderBeer': [
                    'I would like the {type} beer'
                ]
            }
        }
    })
    assert result

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: 'I would like the Grolsch beer'
        })
    await hass.async_block_till_done()

    assert len(intents) == 1
    intent = intents[0]
    assert intent.platform == 'conversation'
    assert intent.intent_type == 'OrderBeer'
    assert intent.slots == {'type': {'value': 'Grolsch'}}
    assert intent.text_input == 'I would like the Grolsch beer'


async def test_register_before_setup(hass):
    """Test calling an intent from a conversation."""
    intents = async_mock_intent(hass, 'OrderBeer')

    hass.components.conversation.async_register('OrderBeer', [
        'A {type} beer, please'
    ])

    result = await async_setup_component(hass, 'conversation', {
        'conversation': {
            'intents': {
                'OrderBeer': [
                    'I would like the {type} beer'
                ]
            }
        }
    })
    assert result

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: 'A Grolsch beer, please'
        })
    await hass.async_block_till_done()

    assert len(intents) == 1
    intent = intents[0]
    assert intent.platform == 'conversation'
    assert intent.intent_type == 'OrderBeer'
    assert intent.slots == {'type': {'value': 'Grolsch'}}
    assert intent.text_input == 'A Grolsch beer, please'

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: 'I would like the Grolsch beer'
        })
    await hass.async_block_till_done()

    assert len(intents) == 2
    intent = intents[1]
    assert intent.platform == 'conversation'
    assert intent.intent_type == 'OrderBeer'
    assert intent.slots == {'type': {'value': 'Grolsch'}}
    assert intent.text_input == 'I would like the Grolsch beer'


async def test_http_processing_intent(hass, aiohttp_client):
    """Test processing intent via HTTP API."""
    class TestIntentHandler(intent.IntentHandler):
        """Test Intent Handler."""

        intent_type = 'OrderBeer'

        async def async_handle(self, intent):
            """Handle the intent."""
            response = intent.create_response()
            response.async_set_speech(
                "I've ordered a {}!".format(intent.slots['type']['value']))
            response.async_set_card(
                "Beer ordered",
                "You chose a {}.".format(intent.slots['type']['value']))
            return response

    intent.async_register(hass, TestIntentHandler())

    result = await async_setup_component(hass, 'conversation', {
        'conversation': {
            'intents': {
                'OrderBeer': [
                    'I would like the {type} beer'
                ]
            }
        }
    })
    assert result

    client = await aiohttp_client(hass.http.app)
    resp = await client.post('/api/conversation/process', json={
        'text': 'I would like the Grolsch beer'
    })

    assert resp.status == 200
    data = await resp.json()

    assert data == {
        'card': {
            'simple': {
                'content': 'You chose a Grolsch.',
                'title': 'Beer ordered'
            }},
        'speech': {
            'plain': {
                'extra_data': None,
                'speech': "I've ordered a Grolsch!"
            }
        }
    }


@pytest.mark.parametrize('sentence', ('turn on kitchen', 'turn kitchen on'))
async def test_turn_on_intent(hass, sentence):
    """Test calling the turn on intent."""
    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {})
    assert result

    hass.states.async_set('light.kitchen', 'off')
    calls = async_mock_service(hass, HASS_DOMAIN, 'turn_on')

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: sentence
        })
    await hass.async_block_till_done()

    assert len(calls) == 1
    call = calls[0]
    assert call.domain == HASS_DOMAIN
    assert call.service == 'turn_on'
    assert call.data == {'entity_id': 'light.kitchen'}


async def test_cover_intents_loading(hass):
    """Test Cover Intents Loading."""
    with pytest.raises(intent.UnknownIntent):
        await intent.async_handle(
            hass, 'test', 'HassOpenCover', {'name': {'value': 'garage door'}}
        )

    result = await async_setup_component(hass, 'cover', {})
    assert result

    hass.states.async_set('cover.garage_door', 'closed')
    calls = async_mock_service(hass, 'cover', SERVICE_OPEN_COVER)

    response = await intent.async_handle(
        hass, 'test', 'HassOpenCover', {'name': {'value': 'garage door'}}
    )
    await hass.async_block_till_done()

    assert response.speech['plain']['speech'] == 'Opened garage door'
    assert len(calls) == 1
    call = calls[0]
    assert call.domain == 'cover'
    assert call.service == 'open_cover'
    assert call.data == {'entity_id': 'cover.garage_door'}


@pytest.mark.parametrize('sentence', ('turn off kitchen', 'turn kitchen off'))
async def test_turn_off_intent(hass, sentence):
    """Test calling the turn on intent."""
    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {})
    assert result

    hass.states.async_set('light.kitchen', 'on')
    calls = async_mock_service(hass, HASS_DOMAIN, 'turn_off')

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: sentence
        })
    await hass.async_block_till_done()

    assert len(calls) == 1
    call = calls[0]
    assert call.domain == HASS_DOMAIN
    assert call.service == 'turn_off'
    assert call.data == {'entity_id': 'light.kitchen'}


@pytest.mark.parametrize('sentence', ('toggle kitchen', 'kitchen toggle'))
async def test_toggle_intent(hass, sentence):
    """Test calling the turn on intent."""
    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {})
    assert result

    hass.states.async_set('light.kitchen', 'on')
    calls = async_mock_service(hass, HASS_DOMAIN, 'toggle')

    await hass.services.async_call(
        'conversation', 'process', {
            conversation.ATTR_TEXT: sentence
        })
    await hass.async_block_till_done()

    assert len(calls) == 1
    call = calls[0]
    assert call.domain == HASS_DOMAIN
    assert call.service == 'toggle'
    assert call.data == {'entity_id': 'light.kitchen'}


async def test_http_api(hass, aiohttp_client):
    """Test the HTTP conversation API."""
    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {})
    assert result

    client = await aiohttp_client(hass.http.app)
    hass.states.async_set('light.kitchen', 'off')
    calls = async_mock_service(hass, HASS_DOMAIN, 'turn_on')

    resp = await client.post('/api/conversation/process', json={
        'text': 'Turn the kitchen on'
    })
    assert resp.status == 200

    assert len(calls) == 1
    call = calls[0]
    assert call.domain == HASS_DOMAIN
    assert call.service == 'turn_on'
    assert call.data == {'entity_id': 'light.kitchen'}


async def test_http_api_wrong_data(hass, aiohttp_client):
    """Test the HTTP conversation API."""
    result = await component.async_setup(hass, {})
    assert result

    result = await async_setup_component(hass, 'conversation', {})
    assert result

    client = await aiohttp_client(hass.http.app)

    resp = await client.post('/api/conversation/process', json={
        'text': 123
    })
    assert resp.status == 400

    resp = await client.post('/api/conversation/process', json={
    })
    assert resp.status == 400


def test_create_matcher():
    """Test the create matcher method."""
    # Basic sentence
    pattern = conversation.create_matcher('Hello world')
    assert pattern.match('Hello world') is not None

    # Match a part
    pattern = conversation.create_matcher('Hello {name}')
    match = pattern.match('hello world')
    assert match is not None
    assert match.groupdict()['name'] == 'world'
    no_match = pattern.match('Hello world, how are you?')
    assert no_match is None

    # Optional and matching part
    pattern = conversation.create_matcher('Turn on [the] {name}')
    match = pattern.match('turn on the kitchen lights')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
    match = pattern.match('turn on kitchen lights')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
    match = pattern.match('turn off kitchen lights')
    assert match is None

    # Two different optional parts, 1 matching part
    pattern = conversation.create_matcher('Turn on [the] [a] {name}')
    match = pattern.match('turn on the kitchen lights')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
    match = pattern.match('turn on kitchen lights')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
    match = pattern.match('turn on a kitchen light')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen light'

    # Strip plural
    pattern = conversation.create_matcher('Turn {name}[s] on')
    match = pattern.match('turn kitchen lights on')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen light'

    # Optional 2 words
    pattern = conversation.create_matcher('Turn [the great] {name} on')
    match = pattern.match('turn the great kitchen lights on')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
    match = pattern.match('turn kitchen lights on')
    assert match is not None
    assert match.groupdict()['name'] == 'kitchen lights'
