"""Tests for the HomeKit component."""
from unittest.mock import patch, ANY, Mock

import pytest

from homeassistant import setup
from homeassistant.core import State
from homeassistant.components.homekit import (
    generate_aid, HomeKit, STATUS_READY, STATUS_RUNNING,
    STATUS_STOPPED, STATUS_WAIT)
from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import (
    CONF_AUTO_START, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START)
from homeassistant.helpers.entityfilter import generate_filter
from homeassistant.const import (
    CONF_IP_ADDRESS, CONF_PORT,
    EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)

from tests.components.homekit.common import patch_debounce

IP_ADDRESS = '127.0.0.1'
PATH_HOMEKIT = 'homeassistant.components.homekit'


@pytest.fixture(scope='module')
def debounce_patcher():
    """Patch debounce method."""
    patcher = patch_debounce()
    yield patcher.start()
    patcher.stop()


def test_generate_aid():
    """Test generate aid method."""
    aid = generate_aid('demo.entity')
    assert isinstance(aid, int)
    assert aid >= 2 and aid <= 18446744073709551615

    with patch(PATH_HOMEKIT + '.adler32') as mock_adler32:
        mock_adler32.side_effect = [0, 1]
        assert generate_aid('demo.entity') is None


async def test_setup_min(hass):
    """Test async_setup with min config options."""
    with patch(PATH_HOMEKIT + '.HomeKit') as mock_homekit:
        assert await setup.async_setup_component(
            hass, DOMAIN, {DOMAIN: {}})

    mock_homekit.assert_any_call(hass, DEFAULT_PORT, None, ANY, {})
    assert mock_homekit().setup.called is True

    # Test auto start enabled
    mock_homekit.reset_mock()
    hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
    await hass.async_block_till_done()

    mock_homekit().start.assert_called_with(ANY)


async def test_setup_auto_start_disabled(hass):
    """Test async_setup with auto start disabled and test service calls."""
    config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111,
                       CONF_IP_ADDRESS: '172.0.0.0'}}

    with patch(PATH_HOMEKIT + '.HomeKit') as mock_homekit:
        mock_homekit.return_value = homekit = Mock()
        assert await setup.async_setup_component(
            hass, DOMAIN, config)

    mock_homekit.assert_any_call(hass, 11111, '172.0.0.0', ANY, {})
    assert mock_homekit().setup.called is True

    # Test auto_start disabled
    homekit.reset_mock()
    hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
    await hass.async_block_till_done()
    assert homekit.start.called is False

    # Test start call with driver is ready
    homekit.reset_mock()
    homekit.status = STATUS_READY

    await hass.services.async_call(
        DOMAIN, SERVICE_HOMEKIT_START, blocking=True)
    assert homekit.start.called is True

    # Test start call with driver started
    homekit.reset_mock()
    homekit.status = STATUS_STOPPED

    await hass.services.async_call(
        DOMAIN, SERVICE_HOMEKIT_START, blocking=True)
    assert homekit.start.called is False


async def test_homekit_setup(hass):
    """Test setup of bridge and driver."""
    homekit = HomeKit(hass, DEFAULT_PORT, None, {}, {})

    with patch(PATH_HOMEKIT + '.accessories.HomeDriver') as mock_driver, \
            patch('homeassistant.util.get_local_ip') as mock_ip:
        mock_ip.return_value = IP_ADDRESS
        await hass.async_add_job(homekit.setup)

    path = hass.config.path(HOMEKIT_FILE)
    assert isinstance(homekit.bridge, HomeBridge)
    mock_driver.assert_called_with(
        hass, homekit.bridge, port=DEFAULT_PORT,
        address=IP_ADDRESS, persist_file=path)

    # Test if stop listener is setup
    assert hass.bus.async_listeners().get(EVENT_HOMEASSISTANT_STOP) == 1


async def test_homekit_setup_ip_address(hass):
    """Test setup with given IP address."""
    homekit = HomeKit(hass, DEFAULT_PORT, '172.0.0.0', {}, {})

    with patch(PATH_HOMEKIT + '.accessories.HomeDriver') as mock_driver:
        await hass.async_add_job(homekit.setup)
    mock_driver.assert_called_with(
        hass, ANY, port=DEFAULT_PORT, address='172.0.0.0', persist_file=ANY)


async def test_homekit_add_accessory():
    """Add accessory if config exists and get_acc returns an accessory."""
    homekit = HomeKit('hass', None, None, lambda entity_id: True, {})
    homekit.bridge = mock_bridge = Mock()

    with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc:

        mock_get_acc.side_effect = [None, 'acc', None]
        homekit.add_bridge_accessory(State('light.demo', 'on'))
        mock_get_acc.assert_called_with('hass', ANY, 363398124, {})
        assert not mock_bridge.add_accessory.called

        homekit.add_bridge_accessory(State('demo.test', 'on'))
        mock_get_acc.assert_called_with('hass', ANY, 294192020, {})
        assert mock_bridge.add_accessory.called

        homekit.add_bridge_accessory(State('demo.test_2', 'on'))
        mock_get_acc.assert_called_with('hass', ANY, 429982757, {})
        mock_bridge.add_accessory.assert_called_with('acc')


async def test_homekit_entity_filter(hass):
    """Test the entity filter."""
    entity_filter = generate_filter(['cover'], ['demo.test'], [], [])
    homekit = HomeKit(hass, None, None, entity_filter, {})

    with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc:
        mock_get_acc.return_value = None

        homekit.add_bridge_accessory(State('cover.test', 'open'))
        assert mock_get_acc.called is True
        mock_get_acc.reset_mock()

        homekit.add_bridge_accessory(State('demo.test', 'on'))
        assert mock_get_acc.called is True
        mock_get_acc.reset_mock()

        homekit.add_bridge_accessory(State('light.demo', 'light'))
        assert mock_get_acc.called is False


async def test_homekit_start(hass, debounce_patcher):
    """Test HomeKit start method."""
    pin = b'123-45-678'
    homekit = HomeKit(hass, None, None, {}, {'cover.demo': {}})
    homekit.bridge = Mock()
    homekit.driver = mock_driver = Mock(state=Mock(paired=False, pincode=pin))

    hass.states.async_set('light.demo', 'on')
    state = hass.states.async_all()[0]

    with patch(PATH_HOMEKIT + '.HomeKit.add_bridge_accessory') as \
        mock_add_acc, \
            patch(PATH_HOMEKIT + '.show_setup_message') as mock_setup_msg:
        await hass.async_add_job(homekit.start)

    mock_add_acc.assert_called_with(state)
    mock_setup_msg.assert_called_with(hass, pin)
    assert mock_driver.start.called is True
    assert homekit.status == STATUS_RUNNING

    # Test start() if already started
    mock_driver.reset_mock()
    await hass.async_add_job(homekit.start)
    assert mock_driver.start.called is False


async def test_homekit_stop(hass):
    """Test HomeKit stop method."""
    homekit = HomeKit(hass, None, None, None, None)
    homekit.driver = Mock()

    assert homekit.status == STATUS_READY
    await hass.async_add_job(homekit.stop)
    homekit.status = STATUS_WAIT
    await hass.async_add_job(homekit.stop)
    homekit.status = STATUS_STOPPED
    await hass.async_add_job(homekit.stop)
    assert homekit.driver.stop.called is False

    # Test if driver is started
    homekit.status = STATUS_RUNNING
    await hass.async_add_job(homekit.stop)
    assert homekit.driver.stop.called is True