"""Test the roon config flow."""
from unittest.mock import patch

from homeassistant import config_entries, setup
from homeassistant.components.roon.const import DOMAIN


class RoonApiMock:
    """Class to mock the Roon API for testing."""

    @property
    def token(self):
        """Return a good authentication key."""
        return "good_token"

    @property
    def core_id(self):
        """Return the roon host."""
        return "core_id"

    def stop(self):
        """Stop socket and discovery."""
        return


class RoonApiMockNoToken(RoonApiMock):
    """Class to mock the Roon API for testing, with failed authorisation."""

    @property
    def token(self):
        """Return a bad authentication key."""
        return None


class RoonApiMockException(RoonApiMock):
    """Class to mock the Roon API for testing, throws an unexpected exception."""

    @property
    def token(self):
        """Throw exception."""
        raise Exception


class RoonDiscoveryMock:
    """Class to mock Roon Discovery for testing."""

    def all(self):
        """Return a discovered roon server."""
        return ["2.2.2.2"]

    def stop(self):
        """Stop discovery running."""
        return


class RoonDiscoveryFailedMock(RoonDiscoveryMock):
    """Class to mock Roon Discovery for testing, with no servers discovered."""

    def all(self):
        """Return no discovered roon servers."""
        return []


async def test_successful_discovery_and_auth(hass):
    """Test when discovery and auth both work ok."""

    await setup.async_setup_component(hass, "persistent_notification", {})
    with patch(
        "homeassistant.components.roon.config_flow.RoonApi",
        return_value=RoonApiMock(),
    ), patch(
        "homeassistant.components.roon.config_flow.RoonDiscovery",
        return_value=RoonDiscoveryMock(),
    ), patch(
        "homeassistant.components.roon.async_setup_entry",
        return_value=True,
    ):

        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        await hass.async_block_till_done()

        # Should go straight to link if server was discovered
        assert result["type"] == "form"
        assert result["step_id"] == "link"
        assert result["errors"] == {}

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

    assert result2["title"] == "Roon Labs Music Player"
    assert result2["data"] == {
        "host": None,
        "api_key": "good_token",
        "roon_server_id": "core_id",
    }


async def test_unsuccessful_discovery_user_form_and_auth(hass):
    """Test unsuccessful discover, user adding the host via the form and then successful auth."""

    await setup.async_setup_component(hass, "persistent_notification", {})
    with patch(
        "homeassistant.components.roon.config_flow.RoonApi",
        return_value=RoonApiMock(),
    ), patch(
        "homeassistant.components.roon.config_flow.RoonDiscovery",
        return_value=RoonDiscoveryFailedMock(),
    ), patch(
        "homeassistant.components.roon.async_setup_entry",
        return_value=True,
    ):

        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        await hass.async_block_till_done()

        # Should show the form if server was not discovered
        assert result["type"] == "form"
        assert result["step_id"] == "user"
        assert result["errors"] == {}

        await hass.config_entries.flow.async_configure(
            result["flow_id"], {"host": "1.1.1.1"}
        )
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input={}
        )
        await hass.async_block_till_done()

    assert result2["title"] == "Roon Labs Music Player"
    assert result2["data"] == {
        "host": "1.1.1.1",
        "api_key": "good_token",
        "roon_server_id": "core_id",
    }


async def test_successful_discovery_no_auth(hass):
    """Test successful discover, but failed auth."""

    await setup.async_setup_component(hass, "persistent_notification", {})
    with patch(
        "homeassistant.components.roon.config_flow.RoonApi",
        return_value=RoonApiMockNoToken(),
    ), patch(
        "homeassistant.components.roon.config_flow.RoonDiscovery",
        return_value=RoonDiscoveryMock(),
    ), patch(
        "homeassistant.components.roon.config_flow.TIMEOUT",
        0,
    ), patch(
        "homeassistant.components.roon.config_flow.AUTHENTICATE_TIMEOUT",
        0.01,
    ), patch(
        "homeassistant.components.roon.async_setup_entry",
        return_value=True,
    ):

        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        await hass.async_block_till_done()

        # Should go straight to link if server was discovered
        assert result["type"] == "form"
        assert result["step_id"] == "link"
        assert result["errors"] == {}

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

    assert result2["errors"] == {"base": "invalid_auth"}


async def test_unexpected_exception(hass):
    """Test successful discover, and unexpected exception during auth."""

    await setup.async_setup_component(hass, "persistent_notification", {})
    with patch(
        "homeassistant.components.roon.config_flow.RoonApi",
        return_value=RoonApiMockException(),
    ), patch(
        "homeassistant.components.roon.config_flow.RoonDiscovery",
        return_value=RoonDiscoveryMock(),
    ), patch(
        "homeassistant.components.roon.async_setup_entry",
        return_value=True,
    ):

        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
        await hass.async_block_till_done()

        # Should go straight to link if server was discovered
        assert result["type"] == "form"
        assert result["step_id"] == "link"
        assert result["errors"] == {}

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

    assert result2["errors"] == {"base": "unknown"}
