"""Test the sma config flow."""

from unittest.mock import AsyncMock, MagicMock, patch

from pysma import SmaAuthenticationException, SmaConnectionException, SmaReadException
from pysma.helpers import DeviceInfo
import pytest

from homeassistant.components.sma.const import CONF_GROUP, DOMAIN
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_SSL, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo

from . import (
    MOCK_DEVICE,
    MOCK_DHCP_DISCOVERY,
    MOCK_DHCP_DISCOVERY_INPUT,
    MOCK_USER_INPUT,
    MOCK_USER_REAUTH,
    MOCK_USER_RECONFIGURE,
)

from tests.conftest import MockConfigEntry

DHCP_DISCOVERY = DhcpServiceInfo(
    ip="1.1.1.1",
    hostname="SMA123456",
    macaddress="0015bb00abcd",
)

DHCP_DISCOVERY_DUPLICATE = DhcpServiceInfo(
    ip="1.1.1.1",
    hostname="SMA123456789",
    macaddress="0015bb00abcd",
)

DHCP_DISCOVERY_DUPLICATE_001 = DhcpServiceInfo(
    ip="1.1.1.1",
    hostname="SMA123456789-001",
    macaddress="0015bb00abcd",
)


async def test_form(
    hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_sma_client: MagicMock
) -> None:
    """Test we get the form."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {}

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        MOCK_USER_INPUT,
    )
    await hass.async_block_till_done()

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == MOCK_USER_INPUT["host"]
    assert result["data"] == MOCK_USER_INPUT

    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (SmaConnectionException, "cannot_connect"),
        (SmaAuthenticationException, "invalid_auth"),
        (SmaReadException, "cannot_retrieve_device_info"),
        (Exception, "unknown"),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_form_exceptions(
    hass: HomeAssistant, exception: Exception, error: str
) -> None:
    """Test we handle cannot connect error."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    with patch(
        "homeassistant.components.sma.config_flow.SMAWebConnect.new_session",
        side_effect=exception,
    ):
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            MOCK_USER_INPUT,
        )

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


@pytest.mark.usefixtures("mock_setup_entry")
async def test_form_already_configured(
    hass: HomeAssistant, mock_sma_client: AsyncMock
) -> None:
    """Test starting a flow by user when already configured."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

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

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


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_discovery(hass: HomeAssistant, mock_sma_client: AsyncMock) -> None:
    """Test we can setup from dhcp discovery."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DHCP_DISCOVERY,
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "discovery_confirm"

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

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == MOCK_DHCP_DISCOVERY["host"]
    assert result["data"] == MOCK_DHCP_DISCOVERY
    assert result["result"].unique_id == DHCP_DISCOVERY.hostname.replace("SMA", "")


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_already_configured(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test starting a flow by dhcp when already configured."""
    mock_config_entry.add_to_hass(hass)
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY_DUPLICATE
    )

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


async def test_dhcp_already_configured_duplicate(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_sma_client: MagicMock,
) -> None:
    """Test starting a flow by DHCP when already configured and MAC is added."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert CONF_MAC not in mock_config_entry.data

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

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"
    await hass.async_block_till_done()

    assert mock_config_entry.data.get(CONF_MAC) == format_mac(
        DHCP_DISCOVERY_DUPLICATE_001.macaddress
    )


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (SmaConnectionException, "cannot_connect"),
        (SmaAuthenticationException, "invalid_auth"),
        (SmaReadException, "cannot_retrieve_device_info"),
        (Exception, "unknown"),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_exceptions(
    hass: HomeAssistant, mock_sma_client: AsyncMock, exception: Exception, error: str
) -> None:
    """Test we handle cannot connect error in DHCP flow."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DHCP_DISCOVERY,
    )

    with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
        mock_sma_instance = mock_sma.return_value
        mock_sma_instance.new_session = AsyncMock(side_effect=exception)

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

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

    with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
        mock_sma_instance = mock_sma.return_value
        mock_sma_instance.new_session = AsyncMock(return_value=True)
        mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
        mock_sma_instance.close_session = AsyncMock(return_value=True)

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

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == MOCK_DHCP_DISCOVERY["host"]
    assert result["data"] == MOCK_DHCP_DISCOVERY
    assert result["result"].unique_id == DHCP_DISCOVERY.hostname.replace("SMA", "")


async def test_full_flow_reauth(
    hass: HomeAssistant, mock_setup_entry: MockConfigEntry, mock_sma_client: AsyncMock
) -> None:
    """Test the full flow of the config flow."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    result = await entry.start_reauth_flow(hass)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"

    # There is no user input
    result = await hass.config_entries.flow.async_configure(result["flow_id"])
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        MOCK_USER_REAUTH,
    )
    await hass.async_block_till_done()

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


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (SmaConnectionException, "cannot_connect"),
        (SmaAuthenticationException, "invalid_auth"),
        (SmaReadException, "cannot_retrieve_device_info"),
        (Exception, "unknown"),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_reauth_flow_exceptions(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    exception: Exception,
    error: str,
) -> None:
    """Test we handle errors during reauth flow properly."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)

    result = await entry.start_reauth_flow(hass)

    with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
        mock_sma_instance = mock_sma.return_value
        mock_sma_instance.new_session = AsyncMock(side_effect=exception)
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            MOCK_USER_REAUTH,
        )

        assert result["type"] is FlowResultType.FORM
        assert result["errors"] == {"base": error}
        assert result["step_id"] == "reauth_confirm"

        mock_sma_instance.new_session = AsyncMock(return_value=True)
        mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
        mock_sma_instance.close_session = AsyncMock(return_value=True)

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            MOCK_USER_REAUTH,
        )
        await hass.async_block_till_done()

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


async def test_full_flow_reconfigure(
    hass: HomeAssistant,
    mock_setup_entry: MockConfigEntry,
    mock_sma_client: AsyncMock,
) -> None:
    """Test the full flow of the config flow."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)
    result = await entry.start_reconfigure_flow(hass)

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input=MOCK_USER_RECONFIGURE,
    )

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "reconfigure_successful"
    assert entry.data[CONF_HOST] == "1.1.1.2"
    assert entry.data[CONF_SSL] is True
    assert entry.data[CONF_VERIFY_SSL] is False
    assert entry.data[CONF_GROUP] == "user"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.parametrize(
    ("exception", "error"),
    [
        (SmaConnectionException, "cannot_connect"),
        (SmaAuthenticationException, "invalid_auth"),
        (SmaReadException, "cannot_retrieve_device_info"),
        (Exception, "unknown"),
    ],
)
async def test_full_flow_reconfigure_exceptions(
    hass: HomeAssistant,
    mock_setup_entry: MockConfigEntry,
    mock_sma_client: AsyncMock,
    exception: Exception,
    error: str,
) -> None:
    """Test we handle cannot connect error and recover from it."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)
    result = await entry.start_reconfigure_flow(hass)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"

    mock_sma_client.new_session.side_effect = exception
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        MOCK_USER_RECONFIGURE,
    )

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

    mock_sma_client.new_session.side_effect = None
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input=MOCK_USER_RECONFIGURE,
    )

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "reconfigure_successful"
    assert entry.data[CONF_HOST] == "1.1.1.2"
    assert entry.data[CONF_SSL] is True
    assert entry.data[CONF_VERIFY_SSL] is False
    assert entry.data[CONF_GROUP] == "user"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_mismatch_id(
    hass: HomeAssistant, mock_sma_client: AsyncMock
) -> None:
    """Test when a mismatch happens during reconfigure."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
    entry.add_to_hass(hass)
    result = await entry.start_reconfigure_flow(hass)

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reconfigure"

    # New device, on purpose to demonstrate we can't switch
    different_device = DeviceInfo(
        manufacturer="SMA",
        name="Different SMA Device",
        type="Sunny Boy 5.0",
        serial=987654321,
        sw_version="2.0.0",
    )
    mock_sma_client.device_info = AsyncMock(return_value=different_device)

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input=MOCK_USER_RECONFIGURE,
    )

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "unique_id_mismatch"
