"""Test state helpers."""
import asyncio
from datetime import timedelta
from unittest.mock import patch

import pytest

import homeassistant.core as ha
from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.util import dt as dt_util
from homeassistant.helpers import state
from homeassistant.const import (
    STATE_OPEN, STATE_CLOSED,
    STATE_LOCKED, STATE_UNLOCKED,
    STATE_ON, STATE_OFF,
    STATE_HOME, STATE_NOT_HOME)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
                                          STATE_BELOW_HORIZON)

from tests.common import async_mock_service


@asyncio.coroutine
def test_async_track_states(hass):
    """Test AsyncTrackStates context manager."""
    point1 = dt_util.utcnow()
    point2 = point1 + timedelta(seconds=5)
    point3 = point2 + timedelta(seconds=5)

    with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow:
        mock_utcnow.return_value = point2

        with state.AsyncTrackStates(hass) as states:
            mock_utcnow.return_value = point1
            hass.states.async_set('light.test', 'on')

            mock_utcnow.return_value = point2
            hass.states.async_set('light.test2', 'on')
            state2 = hass.states.get('light.test2')

            mock_utcnow.return_value = point3
            hass.states.async_set('light.test3', 'on')
            state3 = hass.states.get('light.test3')

    assert [state2, state3] == \
        sorted(states, key=lambda state: state.entity_id)


@asyncio.coroutine
def test_call_to_component(hass):
    """Test calls to components state reproduction functions."""
    with patch(('homeassistant.components.media_player.'
                'async_reproduce_states')) as media_player_fun:
        media_player_fun.return_value = asyncio.Future()
        media_player_fun.return_value.set_result(None)

        with patch(('homeassistant.components.climate.'
                    'async_reproduce_states')) as climate_fun:
            climate_fun.return_value = asyncio.Future()
            climate_fun.return_value.set_result(None)

            state_media_player = ha.State('media_player.test', 'bad')
            state_climate = ha.State('climate.test', 'bad')
            context = "dummy_context"

            yield from state.async_reproduce_state(
                hass,
                [state_media_player, state_climate],
                blocking=True,
                context=context)

            media_player_fun.assert_called_once_with(
                hass,
                [state_media_player],
                context=context)

            climate_fun.assert_called_once_with(
                hass,
                [state_climate],
                context=context)


async def test_get_changed_since(hass):
    """Test get_changed_since."""
    point1 = dt_util.utcnow()
    point2 = point1 + timedelta(seconds=5)
    point3 = point2 + timedelta(seconds=5)

    with patch('homeassistant.core.dt_util.utcnow', return_value=point1):
        hass.states.async_set('light.test', 'on')
        state1 = hass.states.get('light.test')

    with patch('homeassistant.core.dt_util.utcnow', return_value=point2):
        hass.states.async_set('light.test2', 'on')
        state2 = hass.states.get('light.test2')

    with patch('homeassistant.core.dt_util.utcnow', return_value=point3):
        hass.states.async_set('light.test3', 'on')
        state3 = hass.states.get('light.test3')

    assert [state2, state3] == \
        state.get_changed_since([state1, state2, state3], point2)


async def test_reproduce_with_no_entity(hass):
    """Test reproduce_state with no entity."""
    calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)

    await state.async_reproduce_state(hass, ha.State('light.test', 'on'))

    await hass.async_block_till_done()

    assert len(calls) == 0
    assert hass.states.get('light.test') is None


async def test_reproduce_turn_on(hass):
    """Test reproduce_state with SERVICE_TURN_ON."""
    calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)

    hass.states.async_set('light.test', 'off')

    await state.async_reproduce_state(hass, ha.State('light.test', 'on'))

    await hass.async_block_till_done()

    assert len(calls) > 0
    last_call = calls[-1]
    assert last_call.domain == 'light'
    assert SERVICE_TURN_ON == last_call.service
    assert ['light.test'] == last_call.data.get('entity_id')


async def test_reproduce_turn_off(hass):
    """Test reproduce_state with SERVICE_TURN_OFF."""
    calls = async_mock_service(hass, 'light', SERVICE_TURN_OFF)

    hass.states.async_set('light.test', 'on')

    await state.async_reproduce_state(hass, ha.State('light.test', 'off'))

    await hass.async_block_till_done()

    assert len(calls) > 0
    last_call = calls[-1]
    assert last_call.domain == 'light'
    assert SERVICE_TURN_OFF == last_call.service
    assert ['light.test'] == last_call.data.get('entity_id')


async def test_reproduce_complex_data(hass):
    """Test reproduce_state with complex service data."""
    calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)

    hass.states.async_set('light.test', 'off')

    complex_data = ['hello', {'11': '22'}]

    await state.async_reproduce_state(hass, ha.State('light.test', 'on', {
        'complex': complex_data
    }))

    await hass.async_block_till_done()

    assert len(calls) > 0
    last_call = calls[-1]
    assert last_call.domain == 'light'
    assert SERVICE_TURN_ON == last_call.service
    assert complex_data == last_call.data.get('complex')


async def test_reproduce_bad_state(hass):
    """Test reproduce_state with bad state."""
    calls = async_mock_service(hass, 'light', SERVICE_TURN_ON)

    hass.states.async_set('light.test', 'off')

    await state.async_reproduce_state(hass, ha.State('light.test', 'bad'))

    await hass.async_block_till_done()

    assert len(calls) == 0
    assert hass.states.get('light.test').state == 'off'


async def test_as_number_states(hass):
    """Test state_as_number with states."""
    zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED,
                   STATE_BELOW_HORIZON, STATE_NOT_HOME)
    one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON,
                  STATE_HOME)
    for _state in zero_states:
        assert state.state_as_number(ha.State('domain.test', _state, {})) == 0
    for _state in one_states:
        assert state.state_as_number(ha.State('domain.test', _state, {})) == 1


async def test_as_number_coercion(hass):
    """Test state_as_number with number."""
    for _state in ('0', '0.0', 0, 0.0):
        assert state.state_as_number(
            ha.State('domain.test', _state, {})) == 0.0
    for _state in ('1', '1.0', 1, 1.0):
        assert state.state_as_number(
            ha.State('domain.test', _state, {})) == 1.0


async def test_as_number_invalid_cases(hass):
    """Test state_as_number with invalid cases."""
    for _state in ('', 'foo', 'foo.bar', None, False, True, object,
                   object()):
        with pytest.raises(ValueError):
            state.state_as_number(ha.State('domain.test', _state, {}))