"""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
