"""Test Axis device."""
from unittest.mock import Mock, patch

import pytest

from homeassistant.components.axis import device, errors
from homeassistant.components.axis.camera import AxisCamera

from tests.common import MockConfigEntry, mock_coro

DEVICE_DATA = {
    device.CONF_HOST: "1.2.3.4",
    device.CONF_USERNAME: "username",
    device.CONF_PASSWORD: "password",
    device.CONF_PORT: 1234,
}

ENTRY_OPTIONS = {device.CONF_CAMERA: True, device.CONF_EVENTS: True}

ENTRY_CONFIG = {
    device.CONF_DEVICE: DEVICE_DATA,
    device.CONF_MAC: "mac",
    device.CONF_MODEL: "model",
    device.CONF_NAME: "name",
}


async def test_device_setup():
    """Successful setup."""
    hass = Mock()
    entry = Mock()
    entry.data = ENTRY_CONFIG
    entry.options = ENTRY_OPTIONS
    api = Mock()

    axis_device = device.AxisNetworkDevice(hass, entry)
    axis_device.start = Mock()

    assert axis_device.host == DEVICE_DATA[device.CONF_HOST]
    assert axis_device.model == ENTRY_CONFIG[device.CONF_MODEL]
    assert axis_device.name == ENTRY_CONFIG[device.CONF_NAME]
    assert axis_device.serial == ENTRY_CONFIG[device.CONF_MAC]

    with patch.object(device, "get_device", return_value=mock_coro(api)):
        assert await axis_device.async_setup() is True

    assert axis_device.api is api
    assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3
    assert hass.config_entries.async_forward_entry_setup.mock_calls[0][1] == (
        entry,
        "camera",
    )
    assert hass.config_entries.async_forward_entry_setup.mock_calls[1][1] == (
        entry,
        "binary_sensor",
    )
    assert hass.config_entries.async_forward_entry_setup.mock_calls[2][1] == (
        entry,
        "switch",
    )


async def test_device_signal_new_address(hass):
    """Successful setup."""
    entry = MockConfigEntry(
        domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS
    )

    api = Mock()
    api.vapix.get_param.return_value = "1234"

    axis_device = device.AxisNetworkDevice(hass, entry)
    hass.data[device.DOMAIN] = {axis_device.serial: axis_device}

    with patch.object(device, "get_device", return_value=mock_coro(api)), patch.object(
        AxisCamera, "_new_address"
    ) as new_address_mock:
        await axis_device.async_setup()
        await hass.async_block_till_done()

    assert len(hass.states.async_all()) == 1
    assert len(axis_device.listeners) == 2

    entry.data[device.CONF_DEVICE][device.CONF_HOST] = "2.3.4.5"
    hass.config_entries.async_update_entry(entry, data=entry.data)
    await hass.async_block_till_done()

    assert axis_device.host == "2.3.4.5"
    assert axis_device.api.config.host == "2.3.4.5"
    assert len(new_address_mock.mock_calls) == 1


async def test_device_unavailable(hass):
    """Successful setup."""
    entry = MockConfigEntry(
        domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS
    )

    api = Mock()
    api.vapix.get_param.return_value = "1234"

    axis_device = device.AxisNetworkDevice(hass, entry)
    hass.data[device.DOMAIN] = {axis_device.serial: axis_device}

    with patch.object(device, "get_device", return_value=mock_coro(api)), patch.object(
        device, "async_dispatcher_send"
    ) as mock_dispatcher:
        await axis_device.async_setup()
        await hass.async_block_till_done()

        axis_device.async_connection_status_callback(status=False)

    assert not axis_device.available
    assert len(mock_dispatcher.mock_calls) == 1


async def test_device_reset(hass):
    """Successfully reset device."""
    entry = MockConfigEntry(
        domain=device.DOMAIN, data=ENTRY_CONFIG, options=ENTRY_OPTIONS
    )

    api = Mock()
    api.vapix.get_param.return_value = "1234"

    axis_device = device.AxisNetworkDevice(hass, entry)
    hass.data[device.DOMAIN] = {axis_device.serial: axis_device}

    with patch.object(device, "get_device", return_value=mock_coro(api)):
        await axis_device.async_setup()
        await hass.async_block_till_done()

    await axis_device.async_reset()

    assert len(api.stop.mock_calls) == 1
    assert len(hass.states.async_all()) == 0
    assert len(axis_device.listeners) == 0


async def test_device_not_accessible():
    """Failed setup schedules a retry of setup."""
    hass = Mock()
    hass.data = dict()
    entry = Mock()
    entry.data = ENTRY_CONFIG
    entry.options = ENTRY_OPTIONS

    axis_device = device.AxisNetworkDevice(hass, entry)

    with patch.object(
        device, "get_device", side_effect=errors.CannotConnect
    ), pytest.raises(device.ConfigEntryNotReady):
        await axis_device.async_setup()

    assert not hass.helpers.event.async_call_later.mock_calls


async def test_device_unknown_error():
    """Unknown errors are handled."""
    hass = Mock()
    entry = Mock()
    entry.data = ENTRY_CONFIG
    entry.options = ENTRY_OPTIONS

    axis_device = device.AxisNetworkDevice(hass, entry)

    with patch.object(device, "get_device", side_effect=Exception):
        assert await axis_device.async_setup() is False

    assert not hass.helpers.event.async_call_later.mock_calls


async def test_new_event_sends_signal(hass):
    """Make sure that new event send signal."""
    entry = Mock()
    entry.data = ENTRY_CONFIG

    axis_device = device.AxisNetworkDevice(hass, entry)

    with patch.object(device, "async_dispatcher_send") as mock_dispatch_send:
        axis_device.async_event_callback(action="add", event_id="event")
        await hass.async_block_till_done()

    assert len(mock_dispatch_send.mock_calls) == 1
    assert len(mock_dispatch_send.mock_calls[0]) == 3


async def test_shutdown():
    """Successful shutdown."""
    hass = Mock()
    entry = Mock()
    entry.data = ENTRY_CONFIG

    axis_device = device.AxisNetworkDevice(hass, entry)
    axis_device.api = Mock()

    axis_device.shutdown(None)

    assert len(axis_device.api.stop.mock_calls) == 1


async def test_get_device(hass):
    """Successful call."""
    with patch("axis.param_cgi.Params.update_brand", return_value=mock_coro()), patch(
        "axis.param_cgi.Params.update_properties", return_value=mock_coro()
    ), patch("axis.port_cgi.Ports.update", return_value=mock_coro()):
        assert await device.get_device(hass, DEVICE_DATA)


async def test_get_device_fails(hass):
    """Device unauthorized yields authentication required error."""
    import axis

    with patch(
        "axis.param_cgi.Params.update_brand", side_effect=axis.Unauthorized
    ), pytest.raises(errors.AuthenticationRequired):
        await device.get_device(hass, DEVICE_DATA)


async def test_get_device_device_unavailable(hass):
    """Device unavailable yields cannot connect error."""
    import axis

    with patch(
        "axis.param_cgi.Params.update_brand", side_effect=axis.RequestError
    ), pytest.raises(errors.CannotConnect):
        await device.get_device(hass, DEVICE_DATA)


async def test_get_device_unknown_error(hass):
    """Device yield unknown error."""
    import axis

    with patch(
        "axis.param_cgi.Params.update_brand", side_effect=axis.AxisException
    ), pytest.raises(errors.AuthenticationRequired):
        await device.get_device(hass, DEVICE_DATA)
