"""Tests for the LaMetric config flow."""

from http import HTTPStatus
from unittest.mock import MagicMock

from demetriek import (
    LaMetricConnectionError,
    LaMetricConnectionTimeoutError,
    LaMetricError,
    Notification,
    NotificationSound,
    Sound,
)
import pytest

from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.components.lametric.const import DOMAIN
from homeassistant.components.ssdp import (
    ATTR_UPNP_FRIENDLY_NAME,
    ATTR_UPNP_SERIAL,
    SsdpServiceInfo,
)
from homeassistant.config_entries import (
    SOURCE_DHCP,
    SOURCE_REAUTH,
    SOURCE_SSDP,
    SOURCE_USER,
)
from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_entry_oauth2_flow

from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import ClientSessionGenerator

SSDP_DISCOVERY_INFO = SsdpServiceInfo(
    ssdp_usn="mock_usn",
    ssdp_st="mock_st",
    ssdp_location="http://127.0.0.1:44057/465d585b-1c05-444a-b14e-6ffb875b46a6/device_description.xml",
    upnp={
        ATTR_UPNP_FRIENDLY_NAME: "LaMetric Time (LM1245)",
        ATTR_UPNP_SERIAL: "SA110405124500W00BS9",
    },
)


@pytest.mark.usefixtures("current_request_with_host")
async def test_full_cloud_import_flow_multiple_devices(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_setup_entry: MagicMock,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
) -> None:
    """Check a full flow importing from cloud, with multiple devices."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    assert result.get("type") is FlowResultType.MENU
    assert result.get("step_id") == "choice_enter_manual_or_fetch_cloud"
    assert result.get("menu_options") == ["pick_implementation", "manual_entry"]
    flow_id = result["flow_id"]

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )

    assert result2.get("type") is FlowResultType.EXTERNAL_STEP
    assert result2.get("url") == (
        "https://developer.lametric.com/api/v2/oauth2/authorize"
        "?response_type=code&client_id=client"
        "&redirect_uri=https://example.com/auth/external/callback"
        f"&state={state}"
        "&scope=basic+devices_read"
    )

    client = await hass_client_no_auth()
    resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
    assert resp.status == HTTPStatus.OK
    assert resp.headers["content-type"] == "text/html; charset=utf-8"

    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    result3 = await hass.config_entries.flow.async_configure(flow_id)

    assert result3.get("type") is FlowResultType.FORM
    assert result3.get("step_id") == "cloud_select_device"

    result4 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_DEVICE: "SA110405124500W00BS9"}
    )

    assert result4.get("type") is FlowResultType.CREATE_ENTRY
    assert result4.get("title") == "Frenck's LaMetric"
    assert result4.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result4
    assert result4["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("current_request_with_host")
async def test_full_cloud_import_flow_single_device(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_setup_entry: MagicMock,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
) -> None:
    """Check a full flow importing from cloud, with a single device."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    assert result.get("type") is FlowResultType.MENU
    assert result.get("step_id") == "choice_enter_manual_or_fetch_cloud"
    assert result.get("menu_options") == ["pick_implementation", "manual_entry"]
    flow_id = result["flow_id"]

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )

    assert result2.get("type") is FlowResultType.EXTERNAL_STEP
    assert result2.get("url") == (
        "https://developer.lametric.com/api/v2/oauth2/authorize"
        "?response_type=code&client_id=client"
        "&redirect_uri=https://example.com/auth/external/callback"
        f"&state={state}"
        "&scope=basic+devices_read"
    )

    client = await hass_client_no_auth()
    resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
    assert resp.status == HTTPStatus.OK
    assert resp.headers["content-type"] == "text/html; charset=utf-8"

    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    # Stage a single device
    # Should skip step that ask for device selection
    mock_lametric_cloud.devices.return_value = [
        mock_lametric_cloud.devices.return_value[0]
    ]
    result3 = await hass.config_entries.flow.async_configure(flow_id)

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


async def test_full_manual(
    hass: HomeAssistant,
    mock_setup_entry: MagicMock,
    mock_lametric: MagicMock,
) -> None:
    """Check a full flow manual entry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )

    assert result.get("type") is FlowResultType.MENU
    assert result.get("step_id") == "choice_enter_manual_or_fetch_cloud"
    assert result.get("menu_options") == ["pick_implementation", "manual_entry"]
    flow_id = result["flow_id"]

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    assert result2.get("type") is FlowResultType.FORM
    assert result2.get("step_id") == "manual_entry"

    result3 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_HOST: "127.0.0.1", CONF_API_KEY: "mock-api-key"}
    )

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1

    notification: Notification = mock_lametric.notify.mock_calls[0][2]["notification"]
    assert notification.model.sound == Sound(sound=NotificationSound.WIN)

    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("current_request_with_host")
async def test_full_ssdp_with_cloud_import(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_setup_entry: MagicMock,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
) -> None:
    """Check a full flow triggered by SSDP, importing from cloud."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_SSDP}, data=SSDP_DISCOVERY_INFO
    )

    assert result.get("type") is FlowResultType.MENU
    assert result.get("step_id") == "choice_enter_manual_or_fetch_cloud"
    assert result.get("menu_options") == ["pick_implementation", "manual_entry"]
    flow_id = result["flow_id"]

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )

    assert result2.get("type") is FlowResultType.EXTERNAL_STEP
    assert result2.get("url") == (
        "https://developer.lametric.com/api/v2/oauth2/authorize"
        "?response_type=code&client_id=client"
        "&redirect_uri=https://example.com/auth/external/callback"
        f"&state={state}"
        "&scope=basic+devices_read"
    )

    client = await hass_client_no_auth()
    resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
    assert resp.status == HTTPStatus.OK
    assert resp.headers["content-type"] == "text/html; charset=utf-8"

    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    result3 = await hass.config_entries.flow.async_configure(flow_id)

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


async def test_full_ssdp_manual_entry(
    hass: HomeAssistant,
    mock_setup_entry: MagicMock,
    mock_lametric: MagicMock,
) -> None:
    """Check a full flow triggered by SSDP, with manual API key entry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_SSDP}, data=SSDP_DISCOVERY_INFO
    )

    assert result.get("type") is FlowResultType.MENU
    assert result.get("step_id") == "choice_enter_manual_or_fetch_cloud"
    assert result.get("menu_options") == ["pick_implementation", "manual_entry"]
    flow_id = result["flow_id"]

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    assert result2.get("type") is FlowResultType.FORM
    assert result2.get("step_id") == "manual_entry"

    result3 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_API_KEY: "mock-api-key"}
    )

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.parametrize(
    ("data", "reason"),
    [
        (
            SsdpServiceInfo(ssdp_usn="mock_usn", ssdp_st="mock_st", upnp={}),
            "invalid_discovery_info",
        ),
        (
            SsdpServiceInfo(
                ssdp_usn="mock_usn",
                ssdp_st="mock_st",
                ssdp_location="http://169.254.0.1:44057/465d585b-1c05-444a-b14e-6ffb875b46a6/device_description.xml",
                upnp={
                    ATTR_UPNP_SERIAL: "SA110405124500W00BS9",
                },
            ),
            "link_local_address",
        ),
    ],
)
async def test_ssdp_abort_invalid_discovery(
    hass: HomeAssistant, data: SsdpServiceInfo, reason: str
) -> None:
    """Check a full flow triggered by SSDP, with manual API key entry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_SSDP}, data=data
    )
    assert result.get("type") is FlowResultType.ABORT
    assert result.get("reason") == reason


@pytest.mark.usefixtures("current_request_with_host")
async def test_cloud_import_updates_existing_entry(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test cloud importing existing device updates existing entry."""
    mock_config_entry.add_to_hass(hass)

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

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )
    client = await hass_client_no_auth()
    await client.get(f"/auth/external/callback?code=abcd&state={state}")
    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )
    await hass.config_entries.flow.async_configure(flow_id)

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_DEVICE: "SA110405124500W00BS9"}
    )

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "already_configured"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 0


async def test_manual_updates_existing_entry(
    hass: HomeAssistant,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test adding existing device updates existing entry."""
    mock_config_entry.add_to_hass(hass)

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

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    result3 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_HOST: "127.0.0.1", CONF_API_KEY: "mock-api-key"}
    )

    assert result3.get("type") is FlowResultType.ABORT
    assert result3.get("reason") == "already_configured"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 0


async def test_discovery_updates_existing_entry(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test discovery of existing device updates entry."""
    mock_config_entry.add_to_hass(hass)

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

    assert result.get("type") is FlowResultType.ABORT
    assert result.get("reason") == "already_configured"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-from-fixture",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }


@pytest.mark.usefixtures("current_request_with_host")
async def test_cloud_abort_no_devices(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_lametric_cloud: MagicMock,
) -> None:
    """Test cloud importing aborts when account has no devices."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )
    client = await hass_client_no_auth()
    await client.get(f"/auth/external/callback?code=abcd&state={state}")
    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    # Stage there are no devices
    mock_lametric_cloud.devices.return_value = []
    result2 = await hass.config_entries.flow.async_configure(flow_id)

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "no_devices"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1


@pytest.mark.parametrize(
    ("side_effect", "reason"),
    [
        (LaMetricConnectionTimeoutError, "cannot_connect"),
        (LaMetricConnectionError, "cannot_connect"),
        (LaMetricError, "unknown"),
        (RuntimeError, "unknown"),
    ],
)
async def test_manual_errors(
    hass: HomeAssistant,
    mock_lametric: MagicMock,
    mock_setup_entry: MagicMock,
    side_effect: Exception,
    reason: str,
) -> None:
    """Test adding existing device updates existing entry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    mock_lametric.device.side_effect = side_effect
    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_HOST: "127.0.0.1", CONF_API_KEY: "mock-api-key"}
    )

    assert result2.get("type") is FlowResultType.FORM
    assert result2.get("step_id") == "manual_entry"
    assert result2.get("errors") == {"base": reason}

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 0
    assert len(mock_setup_entry.mock_calls) == 0

    mock_lametric.device.side_effect = None
    result3 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_HOST: "127.0.0.1", CONF_API_KEY: "mock-api-key"}
    )

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric.device.mock_calls) == 2
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("current_request_with_host")
@pytest.mark.parametrize(
    ("side_effect", "reason"),
    [
        (LaMetricConnectionTimeoutError, "cannot_connect"),
        (LaMetricConnectionError, "cannot_connect"),
        (LaMetricError, "unknown"),
        (RuntimeError, "unknown"),
    ],
)
async def test_cloud_errors(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_setup_entry: MagicMock,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
    side_effect: Exception,
    reason: str,
) -> None:
    """Test adding existing device updates existing entry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )
    client = await hass_client_no_auth()
    await client.get(f"/auth/external/callback?code=abcd&state={state}")
    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )
    await hass.config_entries.flow.async_configure(flow_id)

    mock_lametric.device.side_effect = side_effect
    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_DEVICE: "SA110405124500W00BS9"}
    )

    assert result2.get("type") is FlowResultType.FORM
    assert result2.get("step_id") == "cloud_select_device"
    assert result2.get("errors") == {"base": reason}

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 0
    assert len(mock_setup_entry.mock_calls) == 0

    mock_lametric.device.side_effect = None
    result3 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_DEVICE: "SA110405124500W00BS9"}
    )

    assert result3.get("type") is FlowResultType.CREATE_ENTRY
    assert result3.get("title") == "Frenck's LaMetric"
    assert result3.get("data") == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }
    assert "result" in result3
    assert result3["result"].unique_id == "SA110405124500W00BS9"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 2
    assert len(mock_lametric.notify.mock_calls) == 1
    assert len(mock_setup_entry.mock_calls) == 1


async def test_dhcp_discovery_updates_entry(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test DHCP discovery updates config entries."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            hostname="lametric",
            ip="127.0.0.42",
            macaddress="aabbccddeeff",
        ),
    )

    assert result.get("type") is FlowResultType.ABORT
    assert result.get("reason") == "already_configured"
    assert mock_config_entry.data == {
        CONF_API_KEY: "mock-from-fixture",
        CONF_HOST: "127.0.0.42",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }


async def test_dhcp_unknown_device(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test unknown DHCP discovery aborts flow."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            hostname="lametric",
            ip="127.0.0.42",
            macaddress="aabbccddee00",
        ),
    )

    assert result.get("type") is FlowResultType.ABORT
    assert result.get("reason") == "unknown"


@pytest.mark.usefixtures("current_request_with_host", "mock_setup_entry")
async def test_reauth_cloud_import(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reauth flow importing api keys from the cloud."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": SOURCE_REAUTH,
            "unique_id": mock_config_entry.unique_id,
            "entry_id": mock_config_entry.entry_id,
        },
        data=mock_config_entry.data,
    )

    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )

    client = await hass_client_no_auth()
    await client.get(f"/auth/external/callback?code=abcd&state={state}")
    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    result2 = await hass.config_entries.flow.async_configure(flow_id)

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "reauth_successful"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1


@pytest.mark.usefixtures("current_request_with_host", "mock_setup_entry")
async def test_reauth_cloud_abort_device_not_found(
    hass: HomeAssistant,
    hass_client_no_auth: ClientSessionGenerator,
    aioclient_mock: AiohttpClientMocker,
    mock_lametric_cloud: MagicMock,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reauth flow importing api keys from the cloud."""
    mock_config_entry.add_to_hass(hass)
    hass.config_entries.async_update_entry(mock_config_entry, unique_id="UKNOWN_DEVICE")

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": SOURCE_REAUTH,
            "unique_id": mock_config_entry.unique_id,
            "entry_id": mock_config_entry.entry_id,
        },
        data=mock_config_entry.data,
    )

    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "pick_implementation"}
    )

    state = config_entry_oauth2_flow._encode_jwt(
        hass,
        {
            "flow_id": flow_id,
            "redirect_uri": "https://example.com/auth/external/callback",
        },
    )

    client = await hass_client_no_auth()
    await client.get(f"/auth/external/callback?code=abcd&state={state}")
    aioclient_mock.post(
        "https://developer.lametric.com/api/v2/oauth2/token",
        json={
            "refresh_token": "mock-refresh-token",
            "access_token": "mock-access-token",
            "type": "Bearer",
            "expires_in": 60,
        },
    )

    result2 = await hass.config_entries.flow.async_configure(flow_id)

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "reauth_device_not_found"

    assert len(mock_lametric_cloud.devices.mock_calls) == 1
    assert len(mock_lametric.device.mock_calls) == 0
    assert len(mock_lametric.notify.mock_calls) == 0


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reauth_manual(
    hass: HomeAssistant,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reauth flow with manual entry."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": SOURCE_REAUTH,
            "unique_id": mock_config_entry.unique_id,
            "entry_id": mock_config_entry.entry_id,
        },
        data=mock_config_entry.data,
    )

    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_API_KEY: "mock-api-key"}
    )

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "reauth_successful"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1


@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize("device_fixture", ["device_sa5"])
async def test_reauth_manual_sky(
    hass: HomeAssistant,
    mock_lametric: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reauth flow with manual entry for LaMetric Sky."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={
            "source": SOURCE_REAUTH,
            "unique_id": mock_config_entry.unique_id,
            "entry_id": mock_config_entry.entry_id,
        },
        data=mock_config_entry.data,
    )

    flow_id = result["flow_id"]

    await hass.config_entries.flow.async_configure(
        flow_id, user_input={"next_step_id": "manual_entry"}
    )

    result2 = await hass.config_entries.flow.async_configure(
        flow_id, user_input={CONF_API_KEY: "mock-api-key"}
    )

    assert result2.get("type") is FlowResultType.ABORT
    assert result2.get("reason") == "reauth_successful"
    assert mock_config_entry.data == {
        CONF_HOST: "127.0.0.1",
        CONF_API_KEY: "mock-api-key",
        CONF_MAC: "AA:BB:CC:DD:EE:FF",
    }

    assert len(mock_lametric.device.mock_calls) == 1
    assert len(mock_lametric.notify.mock_calls) == 1

    notification: Notification = mock_lametric.notify.mock_calls[0][2]["notification"]
    assert notification.model.sound is None