"""Define tests for the Awair config flow."""
from typing import Any
from unittest.mock import Mock, patch

from aiohttp.client_exceptions import ClientConnectorError
from python_awair.exceptions import AuthError, AwairError

from homeassistant import data_entry_flow
from homeassistant.components.awair.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
from homeassistant.core import HomeAssistant

from .const import (
    CLOUD_CONFIG,
    CLOUD_UNIQUE_ID,
    LOCAL_CONFIG,
    LOCAL_UNIQUE_ID,
    ZEROCONF_DISCOVERY,
)

from tests.common import MockConfigEntry


async def test_show_form(hass: HomeAssistant):
    """Test that the form is served with no input."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    assert result["type"] == data_entry_flow.FlowResultType.MENU
    assert result["step_id"] == SOURCE_USER


async def test_invalid_access_token(hass: HomeAssistant):
    """Test that errors are shown when the access token is invalid."""

    with patch("python_awair.AwairClient.query", side_effect=AuthError()):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "cloud"},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            CLOUD_CONFIG,
        )

        assert result["errors"] == {CONF_ACCESS_TOKEN: "invalid_access_token"}


async def test_unexpected_api_error(hass: HomeAssistant):
    """Test that we abort on generic errors."""

    with patch("python_awair.AwairClient.query", side_effect=AwairError()):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "cloud"},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "unknown"


async def test_duplicate_error(hass: HomeAssistant, user, cloud_devices):
    """Test that errors are shown when adding a duplicate config."""

    with patch(
        "python_awair.AwairClient.query",
        side_effect=[user, cloud_devices],
    ):
        MockConfigEntry(
            domain=DOMAIN, unique_id=CLOUD_UNIQUE_ID, data=CLOUD_CONFIG
        ).add_to_hass(hass)

        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "cloud"},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "already_configured_account"


async def test_no_devices_error(hass: HomeAssistant, user, no_devices):
    """Test that errors are shown when the API returns no devices."""

    with patch("python_awair.AwairClient.query", side_effect=[user, no_devices]):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "cloud"},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "no_devices_found"


async def test_reauth(hass: HomeAssistant, user, cloud_devices) -> None:
    """Test reauth flow."""
    mock_config = MockConfigEntry(
        domain=DOMAIN,
        unique_id=CLOUD_UNIQUE_ID,
        data={**CLOUD_CONFIG, CONF_ACCESS_TOKEN: "blah"},
    )
    mock_config.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_REAUTH, "unique_id": CLOUD_UNIQUE_ID},
        data={**CLOUD_CONFIG, CONF_ACCESS_TOKEN: "blah"},
    )
    assert result["type"] == data_entry_flow.FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"
    assert result["errors"] == {}

    with patch("python_awair.AwairClient.query", side_effect=AuthError()):
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            user_input=CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.FORM
        assert result["step_id"] == "reauth_confirm"
        assert result["errors"] == {CONF_ACCESS_TOKEN: "invalid_access_token"}

    with patch(
        "python_awair.AwairClient.query",
        side_effect=[user, cloud_devices],
    ), patch("homeassistant.components.awair.async_setup_entry", return_value=True):
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            user_input=CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "reauth_successful"


async def test_reauth_error(hass: HomeAssistant) -> None:
    """Test reauth flow."""
    mock_config = MockConfigEntry(
        domain=DOMAIN,
        unique_id=CLOUD_UNIQUE_ID,
        data={**CLOUD_CONFIG, CONF_ACCESS_TOKEN: "blah"},
    )
    mock_config.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_REAUTH, "unique_id": CLOUD_UNIQUE_ID},
        data={**CLOUD_CONFIG, CONF_ACCESS_TOKEN: "blah"},
    )
    assert result["type"] == data_entry_flow.FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"
    assert result["errors"] == {}

    with patch("python_awair.AwairClient.query", side_effect=AwairError()):
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            user_input=CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "unknown"


async def test_create_cloud_entry(hass: HomeAssistant, user, cloud_devices):
    """Test overall flow when using cloud api."""

    with patch(
        "python_awair.AwairClient.query",
        side_effect=[user, cloud_devices],
    ), patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "cloud"},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            CLOUD_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
        assert result["title"] == "foo@bar.com"
        assert result["data"][CONF_ACCESS_TOKEN] == CLOUD_CONFIG[CONF_ACCESS_TOKEN]
        assert result["result"].unique_id == CLOUD_UNIQUE_ID


async def test_create_local_entry(hass: HomeAssistant, local_devices):
    """Test overall flow when using local API."""

    with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=LOCAL_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "local"},
        )

        # We're being shown the local instructions
        form_step = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            {},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            LOCAL_CONFIG,
        )

        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
        assert result["title"] == "Awair Element (24947)"
        assert result["data"][CONF_HOST] == LOCAL_CONFIG[CONF_HOST]
        assert result["result"].unique_id == LOCAL_UNIQUE_ID


async def test_create_local_entry_from_discovery(hass: HomeAssistant, local_devices):
    """Test local API when device discovered after instructions shown."""

    menu_step = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}, data=LOCAL_CONFIG
    )

    form_step = await hass.config_entries.flow.async_configure(
        menu_step["flow_id"],
        {"next_step_id": "local"},
    )

    # Create discovered entry in progress
    with patch("python_awair.AwairClient.query", side_effect=[local_devices]):
        await hass.config_entries.flow.async_init(
            DOMAIN,
            data=Mock(host=LOCAL_CONFIG[CONF_HOST]),
            context={"source": SOURCE_ZEROCONF},
        )

    # We're being shown the local instructions
    form_step = await hass.config_entries.flow.async_configure(
        form_step["flow_id"],
        {},
    )

    with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ):
        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            {"device": LOCAL_CONFIG[CONF_HOST]},
        )

    assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
    assert result["title"] == "Awair Element (24947)"
    assert result["data"][CONF_HOST] == LOCAL_CONFIG[CONF_HOST]
    assert result["result"].unique_id == LOCAL_UNIQUE_ID


async def test_create_local_entry_awair_error(hass: HomeAssistant):
    """Test overall flow when using local API and device is returns error."""

    with patch(
        "python_awair.AwairClient.query",
        side_effect=AwairError(),
    ):
        menu_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_USER}, data=LOCAL_CONFIG
        )

        form_step = await hass.config_entries.flow.async_configure(
            menu_step["flow_id"],
            {"next_step_id": "local"},
        )

        # We're being shown the local instructions
        form_step = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            {},
        )

        result = await hass.config_entries.flow.async_configure(
            form_step["flow_id"],
            LOCAL_CONFIG,
        )

        # User is returned to form to try again
        assert result["type"] == data_entry_flow.FlowResultType.FORM
        assert result["step_id"] == "local_pick"


async def test_create_zeroconf_entry(hass: HomeAssistant, local_devices):
    """Test overall flow when using discovery."""

    with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ):
        confirm_step = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_ZEROCONF}, data=ZEROCONF_DISCOVERY
        )

        result = await hass.config_entries.flow.async_configure(
            confirm_step["flow_id"],
            {},
        )

        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
        assert result["title"] == "Awair Element (24947)"
        assert result["data"][CONF_HOST] == ZEROCONF_DISCOVERY.host
        assert result["result"].unique_id == LOCAL_UNIQUE_ID


async def test_unsuccessful_create_zeroconf_entry(hass: HomeAssistant):
    """Test overall flow when using discovery and device is unreachable."""

    with patch(
        "python_awair.AwairClient.query",
        side_effect=ClientConnectorError(Mock(), OSError()),
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_ZEROCONF}, data=ZEROCONF_DISCOVERY
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT


async def test_zeroconf_discovery_update_configuration(
    hass: HomeAssistant, local_devices: Any
) -> None:
    """Test updating an existing Awair config entry with discovery info."""

    config_entry = MockConfigEntry(
        domain=DOMAIN,
        data={CONF_HOST: "127.0.0.1"},
        unique_id=LOCAL_UNIQUE_ID,
    )
    config_entry.add_to_hass(hass)

    with patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry, patch(
        "python_awair.AwairClient.query", side_effect=[local_devices]
    ), patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_ZEROCONF},
            data=ZEROCONF_DISCOVERY,
        )

        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "already_configured_device"

        assert config_entry.data[CONF_HOST] == ZEROCONF_DISCOVERY.host
        assert mock_setup_entry.call_count == 0


async def test_zeroconf_during_onboarding(
    hass: HomeAssistant, local_devices: Any
) -> None:
    """Test the zeroconf creates an entry during onboarding."""
    with patch(
        "homeassistant.components.awair.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry, patch(
        "python_awair.AwairClient.query", side_effect=[local_devices]
    ), patch(
        "homeassistant.components.onboarding.async_is_onboarded",
        return_value=False,
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": SOURCE_ZEROCONF}, data=ZEROCONF_DISCOVERY
        )

    assert result.get("type") == data_entry_flow.FlowResultType.CREATE_ENTRY
    assert result.get("title") == "Awair Element (24947)"
    assert "data" in result
    assert result["data"][CONF_HOST] == ZEROCONF_DISCOVERY.host
    assert "result" in result
    assert result["result"].unique_id == LOCAL_UNIQUE_ID
    assert len(mock_setup_entry.mock_calls) == 1