"""The tests for the litejet component."""
import logging
from unittest import mock
from datetime import timedelta
import pytest

from homeassistant import setup
import homeassistant.util.dt as dt_util
from homeassistant.components import litejet
import homeassistant.components.automation as automation

from tests.common import (async_fire_time_changed, async_mock_service)

_LOGGER = logging.getLogger(__name__)

ENTITY_SWITCH = 'switch.mock_switch_1'
ENTITY_SWITCH_NUMBER = 1
ENTITY_OTHER_SWITCH = 'switch.mock_switch_2'
ENTITY_OTHER_SWITCH_NUMBER = 2


@pytest.fixture
def calls(hass):
    """Track calls to a mock serivce."""
    return async_mock_service(hass, 'test', 'automation')


def get_switch_name(number):
    """Get a mock switch name."""
    return "Mock Switch #"+str(number)


@pytest.fixture
def mock_lj(hass):
    """Initialize components."""
    with mock.patch('pylitejet.LiteJet') as mock_pylitejet:
        mock_lj = mock_pylitejet.return_value

        mock_lj.switch_pressed_callbacks = {}
        mock_lj.switch_released_callbacks = {}

        def on_switch_pressed(number, callback):
            mock_lj.switch_pressed_callbacks[number] = callback

        def on_switch_released(number, callback):
            mock_lj.switch_released_callbacks[number] = callback

        mock_lj.loads.return_value = range(0)
        mock_lj.button_switches.return_value = range(1, 3)
        mock_lj.all_switches.return_value = range(1, 6)
        mock_lj.scenes.return_value = range(0)
        mock_lj.get_switch_name.side_effect = get_switch_name
        mock_lj.on_switch_pressed.side_effect = on_switch_pressed
        mock_lj.on_switch_released.side_effect = on_switch_released

        config = {
            'litejet': {
                'port': '/tmp/this_will_be_mocked'
            }
        }
        assert hass.loop.run_until_complete(setup.async_setup_component(
            hass, litejet.DOMAIN, config))

        mock_lj.start_time = dt_util.utcnow()
        mock_lj.last_delta = timedelta(0)
        return mock_lj


async def simulate_press(hass, mock_lj, number):
    """Test to simulate a press."""
    _LOGGER.info('*** simulate press of %d', number)
    callback = mock_lj.switch_pressed_callbacks.get(number)
    with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
                    return_value=mock_lj.start_time + mock_lj.last_delta):
        if callback is not None:
            await hass.async_add_job(callback)
        await hass.async_block_till_done()


async def simulate_release(hass, mock_lj, number):
    """Test to simulate releasing."""
    _LOGGER.info('*** simulate release of %d', number)
    callback = mock_lj.switch_released_callbacks.get(number)
    with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
                    return_value=mock_lj.start_time + mock_lj.last_delta):
        if callback is not None:
            await hass.async_add_job(callback)
        await hass.async_block_till_done()


async def simulate_time(hass, mock_lj, delta):
    """Test to simulate time."""
    _LOGGER.info(
        '*** simulate time change by %s: %s',
        delta,
        mock_lj.start_time + delta)
    mock_lj.last_delta = delta
    with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
                    return_value=mock_lj.start_time + delta):
        _LOGGER.info('now=%s', dt_util.utcnow())
        async_fire_time_changed(hass, mock_lj.start_time + delta)
        await hass.async_block_till_done()
        _LOGGER.info('done with now=%s', dt_util.utcnow())


async def setup_automation(hass, trigger):
    """Test setting up the automation."""
    assert await setup.async_setup_component(hass, automation.DOMAIN, {
        automation.DOMAIN: [
            {
                'alias': 'My Test',
                'trigger': trigger,
                'action': {
                    'service': 'test.automation'
                }
            }
        ]
    })
    await hass.async_block_till_done()


async def test_simple(hass, calls, mock_lj):
    """Test the simplest form of a LiteJet trigger."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)

    assert len(calls) == 1


async def test_held_more_than_short(hass, calls, mock_lj):
    """Test a too short hold."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_more_than': {
            'milliseconds': '200'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    await simulate_time(hass, mock_lj, timedelta(seconds=0.1))
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0


async def test_held_more_than_long(hass, calls, mock_lj):
    """Test a hold that is long enough."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_more_than': {
            'milliseconds': '200'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0
    await simulate_time(hass, mock_lj, timedelta(seconds=0.3))
    assert len(calls) == 1
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 1


async def test_held_less_than_short(hass, calls, mock_lj):
    """Test a hold that is short enough."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_less_than': {
            'milliseconds': '200'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    await simulate_time(hass, mock_lj, timedelta(seconds=0.1))
    assert len(calls) == 0
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 1


async def test_held_less_than_long(hass, calls, mock_lj):
    """Test a hold that is too long."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_less_than': {
            'milliseconds': '200'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0
    await simulate_time(hass, mock_lj, timedelta(seconds=0.3))
    assert len(calls) == 0
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0


async def test_held_in_range_short(hass, calls, mock_lj):
    """Test an in-range trigger with a too short hold."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_more_than': {
            'milliseconds': '100'
        },
        'held_less_than': {
            'milliseconds': '300'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    await simulate_time(hass, mock_lj, timedelta(seconds=0.05))
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0


async def test_held_in_range_just_right(hass, calls, mock_lj):
    """Test an in-range trigger with a just right hold."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_more_than': {
            'milliseconds': '100'
        },
        'held_less_than': {
            'milliseconds': '300'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0
    await simulate_time(hass, mock_lj, timedelta(seconds=0.2))
    assert len(calls) == 0
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 1


async def test_held_in_range_long(hass, calls, mock_lj):
    """Test an in-range trigger with a too long hold."""
    await setup_automation(hass, {
        'platform': 'litejet',
        'number': ENTITY_OTHER_SWITCH_NUMBER,
        'held_more_than': {
            'milliseconds': '100'
        },
        'held_less_than': {
            'milliseconds': '300'
        }
    })

    await simulate_press(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0
    await simulate_time(hass, mock_lj, timedelta(seconds=0.4))
    assert len(calls) == 0
    await simulate_release(hass, mock_lj, ENTITY_OTHER_SWITCH_NUMBER)
    assert len(calls) == 0