"""Test the Open Exchange Rates config flow."""
import asyncio
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch

from aioopenexchangerates import (
    OpenExchangeRatesAuthError,
    OpenExchangeRatesClientError,
)
import pytest

from homeassistant import config_entries
from homeassistant.components.openexchangerates.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from tests.common import MockConfigEntry


@pytest.fixture(name="currencies", autouse=True)
def currencies_fixture(hass: HomeAssistant) -> Generator[AsyncMock, None, None]:
    """Mock currencies."""
    with patch(
        "homeassistant.components.openexchangerates.config_flow.Client.get_currencies",
        return_value={"USD": "United States Dollar", "EUR": "Euro"},
    ) as mock_currencies:
        yield mock_currencies


async def test_user_create_entry(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
    mock_setup_entry: AsyncMock,
) -> None:
    """Test we get the form."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] == FlowResultType.FORM
    assert result["errors"] is None

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"api_key": "test-api-key"},
    )
    await hass.async_block_till_done()

    assert result["type"] == FlowResultType.CREATE_ENTRY
    assert result["title"] == "USD"
    assert result["data"] == {
        "api_key": "test-api-key",
        "base": "USD",
    }
    assert len(mock_setup_entry.mock_calls) == 1


async def test_form_invalid_auth(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
) -> None:
    """Test we handle invalid auth."""
    mock_latest_rates_config_flow.side_effect = OpenExchangeRatesAuthError()
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"api_key": "bad-api-key"},
    )

    assert result["type"] == FlowResultType.FORM
    assert result["errors"] == {"base": "invalid_auth"}


async def test_form_cannot_connect(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
) -> None:
    """Test we handle cannot connect error."""
    mock_latest_rates_config_flow.side_effect = OpenExchangeRatesClientError()
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"api_key": "test-api-key"},
    )

    assert result["type"] == FlowResultType.FORM
    assert result["errors"] == {"base": "cannot_connect"}


async def test_form_unknown_error(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
) -> None:
    """Test we handle unknown error."""
    mock_latest_rates_config_flow.side_effect = Exception()
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"api_key": "test-api-key"},
    )

    assert result["type"] == FlowResultType.FORM
    assert result["errors"] == {"base": "unknown"}


async def test_already_configured_service(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test we abort if the service is already configured."""
    mock_config_entry.add_to_hass(hass)
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] == FlowResultType.FORM
    assert result["errors"] is None

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {"api_key": "test-api-key"},
    )

    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "already_configured"


async def test_no_currencies(hass: HomeAssistant, currencies: AsyncMock) -> None:
    """Test we abort if the service fails to retrieve currencies."""
    currencies.side_effect = OpenExchangeRatesClientError()
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "cannot_connect"


async def test_currencies_timeout(hass: HomeAssistant, currencies: AsyncMock) -> None:
    """Test we abort if the service times out retrieving currencies."""

    async def currencies_side_effect():
        await asyncio.sleep(1)
        return {"USD": "United States Dollar", "EUR": "Euro"}

    currencies.side_effect = currencies_side_effect

    with patch(
        "homeassistant.components.openexchangerates.config_flow.CLIENT_TIMEOUT", 0
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN, context={"source": config_entries.SOURCE_USER}
        )
    assert result["type"] == FlowResultType.ABORT
    assert result["reason"] == "timeout_connect"


async def test_latest_rates_timeout(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
) -> None:
    """Test we abort if the service times out retrieving latest rates."""

    async def latest_rates_side_effect(*args: Any, **kwargs: Any) -> dict[str, float]:
        await asyncio.sleep(1)
        return {"EUR": 1.0}

    mock_latest_rates_config_flow.side_effect = latest_rates_side_effect

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

    with patch(
        "homeassistant.components.openexchangerates.config_flow.CLIENT_TIMEOUT", 0
    ):
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {"api_key": "test-api-key"},
        )

    assert result["type"] == FlowResultType.FORM
    assert result["errors"] == {"base": "timeout_connect"}


async def test_reauth(
    hass: HomeAssistant,
    mock_latest_rates_config_flow: AsyncMock,
    mock_setup_entry: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test we can reauthenticate the config entry."""
    mock_config_entry.add_to_hass(hass)
    flow_context = {
        "source": config_entries.SOURCE_REAUTH,
        "entry_id": mock_config_entry.entry_id,
        "title_placeholders": {"name": mock_config_entry.title},
        "unique_id": mock_config_entry.unique_id,
    }

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context=flow_context, data=mock_config_entry.data
    )
    assert result["type"] == FlowResultType.FORM
    assert result["errors"] is None

    mock_latest_rates_config_flow.side_effect = OpenExchangeRatesAuthError()

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            "api_key": "invalid-test-api-key",
        },
    )

    assert result["type"] == FlowResultType.FORM
    assert result["errors"] == {"base": "invalid_auth"}

    mock_latest_rates_config_flow.side_effect = None

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            "api_key": "new-test-api-key",
        },
    )
    await hass.async_block_till_done()

    assert result["type"] == "abort"
    assert result["reason"] == "reauth_successful"
    assert len(mock_setup_entry.mock_calls) == 1
