"""Tests for Philips Hue config flow."""
import asyncio
from unittest.mock import Mock, patch

import aiohue
import pytest
import voluptuous as vol

from homeassistant.components.hue import config_flow, const, errors

from tests.common import MockConfigEntry, mock_coro


async def test_flow_works(hass, aioclient_mock):
    """Test config flow ."""
    aioclient_mock.get(
        const.API_NUPNP, json=[{"internalipaddress": "1.2.3.4", "id": "bla"}]
    )

    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    await flow.async_step_init()

    with patch("aiohue.Bridge") as mock_bridge:

        def mock_constructor(host, websession, username=None):
            """Fake the bridge constructor."""
            mock_bridge.host = host
            return mock_bridge

        mock_bridge.side_effect = mock_constructor
        mock_bridge.username = "username-abc"
        mock_bridge.config.name = "Mock Bridge"
        mock_bridge.config.bridgeid = "bridge-id-1234"
        mock_bridge.create_user.return_value = mock_coro()
        mock_bridge.initialize.return_value = mock_coro()

        result = await flow.async_step_link(user_input={})

    assert mock_bridge.host == "1.2.3.4"
    assert len(mock_bridge.create_user.mock_calls) == 1
    assert len(mock_bridge.initialize.mock_calls) == 1

    assert result["type"] == "create_entry"
    assert result["title"] == "Mock Bridge"
    assert result["data"] == {
        "host": "1.2.3.4",
        "bridge_id": "bridge-id-1234",
        "username": "username-abc",
    }


async def test_flow_no_discovered_bridges(hass, aioclient_mock):
    """Test config flow discovers no bridges."""
    aioclient_mock.get(const.API_NUPNP, json=[])
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_init()
    assert result["type"] == "abort"


async def test_flow_all_discovered_bridges_exist(hass, aioclient_mock):
    """Test config flow discovers only already configured bridges."""
    aioclient_mock.get(
        const.API_NUPNP, json=[{"internalipaddress": "1.2.3.4", "id": "bla"}]
    )
    MockConfigEntry(domain="hue", data={"host": "1.2.3.4"}).add_to_hass(hass)
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_init()
    assert result["type"] == "abort"


async def test_flow_one_bridge_discovered(hass, aioclient_mock):
    """Test config flow discovers one bridge."""
    aioclient_mock.get(
        const.API_NUPNP, json=[{"internalipaddress": "1.2.3.4", "id": "bla"}]
    )
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_init()
    assert result["type"] == "form"
    assert result["step_id"] == "link"


async def test_flow_two_bridges_discovered(hass, aioclient_mock):
    """Test config flow discovers two bridges."""
    aioclient_mock.get(
        const.API_NUPNP,
        json=[
            {"internalipaddress": "1.2.3.4", "id": "bla"},
            {"internalipaddress": "5.6.7.8", "id": "beer"},
        ],
    )
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_init()
    assert result["type"] == "form"
    assert result["step_id"] == "init"

    with pytest.raises(vol.Invalid):
        assert result["data_schema"]({"host": "0.0.0.0"})

    result["data_schema"]({"host": "1.2.3.4"})
    result["data_schema"]({"host": "5.6.7.8"})


async def test_flow_two_bridges_discovered_one_new(hass, aioclient_mock):
    """Test config flow discovers two bridges."""
    aioclient_mock.get(
        const.API_NUPNP,
        json=[
            {"internalipaddress": "1.2.3.4", "id": "bla"},
            {"internalipaddress": "5.6.7.8", "id": "beer"},
        ],
    )
    MockConfigEntry(domain="hue", data={"host": "1.2.3.4"}).add_to_hass(hass)
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_init()
    assert result["type"] == "form"
    assert result["step_id"] == "link"
    assert flow.host == "5.6.7.8"


async def test_flow_timeout_discovery(hass):
    """Test config flow ."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    with patch(
        "homeassistant.components.hue.config_flow.discover_nupnp",
        side_effect=asyncio.TimeoutError,
    ):
        result = await flow.async_step_init()

    assert result["type"] == "abort"


async def test_flow_link_timeout(hass):
    """Test config flow ."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    with patch("aiohue.Bridge.create_user", side_effect=asyncio.TimeoutError):
        result = await flow.async_step_link({})

    assert result["type"] == "form"
    assert result["step_id"] == "link"
    assert result["errors"] == {"base": "linking"}


async def test_flow_link_button_not_pressed(hass):
    """Test config flow ."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    with patch("aiohue.Bridge.create_user", side_effect=aiohue.LinkButtonNotPressed):
        result = await flow.async_step_link({})

    assert result["type"] == "form"
    assert result["step_id"] == "link"
    assert result["errors"] == {"base": "register_failed"}


async def test_flow_link_unknown_host(hass):
    """Test config flow ."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    with patch("aiohue.Bridge.create_user", side_effect=aiohue.RequestError):
        result = await flow.async_step_link({})

    assert result["type"] == "form"
    assert result["step_id"] == "link"
    assert result["errors"] == {"base": "linking"}


async def test_bridge_ssdp(hass):
    """Test a bridge being discovered."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    with patch.object(
        config_flow, "get_bridge", side_effect=errors.AuthenticationRequired
    ):
        result = await flow.async_step_ssdp(
            {
                "host": "0.0.0.0",
                "serial": "1234",
                "manufacturerURL": config_flow.HUE_MANUFACTURERURL,
            }
        )

    assert result["type"] == "form"
    assert result["step_id"] == "link"


async def test_bridge_ssdp_discover_other_bridge(hass):
    """Test that discovery ignores other bridges."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass

    result = await flow.async_step_ssdp(
        {"manufacturerURL": "http://www.notphilips.com"}
    )

    assert result["type"] == "abort"


async def test_bridge_ssdp_emulated_hue(hass):
    """Test if discovery info is from an emulated hue instance."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    result = await flow.async_step_ssdp(
        {
            "name": "HASS Bridge",
            "host": "0.0.0.0",
            "serial": "1234",
            "manufacturerURL": config_flow.HUE_MANUFACTURERURL,
        }
    )

    assert result["type"] == "abort"


async def test_bridge_ssdp_already_configured(hass):
    """Test if a discovered bridge has already been configured."""
    MockConfigEntry(domain="hue", data={"host": "0.0.0.0"}).add_to_hass(hass)

    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    result = await flow.async_step_ssdp(
        {
            "host": "0.0.0.0",
            "serial": "1234",
            "manufacturerURL": config_flow.HUE_MANUFACTURERURL,
        }
    )

    assert result["type"] == "abort"


async def test_import_with_existing_config(hass):
    """Test importing a host with an existing config file."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    bridge = Mock()
    bridge.username = "username-abc"
    bridge.config.bridgeid = "bridge-id-1234"
    bridge.config.name = "Mock Bridge"
    bridge.host = "0.0.0.0"

    with patch.object(
        config_flow, "_find_username_from_config", return_value="mock-user"
    ), patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)):
        result = await flow.async_step_import({"host": "0.0.0.0", "path": "bla.conf"})

    assert result["type"] == "create_entry"
    assert result["title"] == "Mock Bridge"
    assert result["data"] == {
        "host": "0.0.0.0",
        "bridge_id": "bridge-id-1234",
        "username": "username-abc",
    }


async def test_import_with_no_config(hass):
    """Test importing a host without an existing config file."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    with patch.object(
        config_flow, "get_bridge", side_effect=errors.AuthenticationRequired
    ):
        result = await flow.async_step_import({"host": "0.0.0.0"})

    assert result["type"] == "form"
    assert result["step_id"] == "link"


async def test_import_with_existing_but_invalid_config(hass):
    """Test importing a host with a config file with invalid username."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    with patch.object(
        config_flow, "_find_username_from_config", return_value="mock-user"
    ), patch.object(
        config_flow, "get_bridge", side_effect=errors.AuthenticationRequired
    ):
        result = await flow.async_step_import({"host": "0.0.0.0", "path": "bla.conf"})

    assert result["type"] == "form"
    assert result["step_id"] == "link"


async def test_import_cannot_connect(hass):
    """Test importing a host that we cannot conncet to."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    with patch.object(config_flow, "get_bridge", side_effect=errors.CannotConnect):
        result = await flow.async_step_import({"host": "0.0.0.0"})

    assert result["type"] == "abort"
    assert result["reason"] == "cannot_connect"


async def test_creating_entry_removes_entries_for_same_host_or_bridge(hass):
    """Test that we clean up entries for same host and bridge.

    An IP can only hold a single bridge and a single bridge can only be
    accessible via a single IP. So when we create a new entry, we'll remove
    all existing entries that either have same IP or same bridge_id.
    """
    MockConfigEntry(
        domain="hue", data={"host": "0.0.0.0", "bridge_id": "id-1234"}
    ).add_to_hass(hass)

    MockConfigEntry(
        domain="hue", data={"host": "1.2.3.4", "bridge_id": "id-1234"}
    ).add_to_hass(hass)

    assert len(hass.config_entries.async_entries("hue")) == 2

    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    bridge = Mock()
    bridge.username = "username-abc"
    bridge.config.bridgeid = "id-1234"
    bridge.config.name = "Mock Bridge"
    bridge.host = "0.0.0.0"

    with patch.object(config_flow, "get_bridge", return_value=mock_coro(bridge)):
        result = await flow.async_step_import({"host": "0.0.0.0"})

    assert result["type"] == "create_entry"
    assert result["title"] == "Mock Bridge"
    assert result["data"] == {
        "host": "0.0.0.0",
        "bridge_id": "id-1234",
        "username": "username-abc",
    }
    # We did not process the result of this entry but already removed the old
    # ones. So we should have 0 entries.
    assert len(hass.config_entries.async_entries("hue")) == 0


async def test_bridge_homekit(hass):
    """Test a bridge being discovered via HomeKit."""
    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    with patch.object(
        config_flow, "get_bridge", side_effect=errors.AuthenticationRequired
    ):
        result = await flow.async_step_homekit(
            {
                "host": "0.0.0.0",
                "serial": "1234",
                "manufacturerURL": config_flow.HUE_MANUFACTURERURL,
            }
        )

    assert result["type"] == "form"
    assert result["step_id"] == "link"


async def test_bridge_homekit_already_configured(hass):
    """Test if a HomeKit discovered bridge has already been configured."""
    MockConfigEntry(domain="hue", data={"host": "0.0.0.0"}).add_to_hass(hass)

    flow = config_flow.HueFlowHandler()
    flow.hass = hass
    flow.context = {}

    result = await flow.async_step_homekit({"host": "0.0.0.0"})

    assert result["type"] == "abort"
