"""Config flow for EHEIM Digital."""

from __future__ import annotations

import asyncio
from typing import TYPE_CHECKING, Any

from aiohttp import ClientError
from eheimdigital.device import EheimDigitalDevice
from eheimdigital.hub import EheimDigitalHub
import voluptuous as vol

from homeassistant.config_entries import (
    SOURCE_RECONFIGURE,
    SOURCE_USER,
    ConfigFlow,
    ConfigFlowResult,
)
from homeassistant.const import CONF_HOST
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import DOMAIN, LOGGER

CONFIG_SCHEMA = vol.Schema(
    {vol.Required(CONF_HOST, default="eheimdigital.local"): selector.TextSelector()}
)


class EheimDigitalConfigFlow(ConfigFlow, domain=DOMAIN):
    """The EHEIM Digital config flow."""

    def __init__(self) -> None:
        """Initialize the config flow."""
        super().__init__()
        self.data: dict[str, Any] = {}
        self.main_device_added_event = asyncio.Event()

    async def async_step_zeroconf(
        self, discovery_info: ZeroconfServiceInfo
    ) -> ConfigFlowResult:
        """Handle zeroconf discovery."""
        self.data[CONF_HOST] = host = discovery_info.host

        self._async_abort_entries_match(self.data)

        hub = EheimDigitalHub(
            host=host,
            session=async_get_clientsession(self.hass),
            loop=self.hass.loop,
            main_device_added_event=self.main_device_added_event,
        )
        try:
            await hub.connect()

            async with asyncio.timeout(2):
                # This event gets triggered when the first message is received from
                # the device, it contains the data necessary to create the main device.
                # This removes the race condition where the main device is accessed
                # before the response from the device is parsed.
                await self.main_device_added_event.wait()
                if TYPE_CHECKING:
                    # At this point the main device is always set
                    assert isinstance(hub.main, EheimDigitalDevice)
                await hub.close()
        except (ClientError, TimeoutError):
            return self.async_abort(reason="cannot_connect")
        except Exception:  # noqa: BLE001
            LOGGER.exception("Unknown exception occurred")
            return self.async_abort(reason="unknown")
        await self.async_set_unique_id(hub.main.mac_address)
        self._abort_if_unique_id_configured(updates={CONF_HOST: host})
        return await self.async_step_discovery_confirm()

    async def async_step_discovery_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Confirm discovery."""
        if user_input is not None:
            return self.async_create_entry(
                title=self.data[CONF_HOST],
                data={CONF_HOST: self.data[CONF_HOST]},
            )

        self._set_confirm_only()
        return self.async_show_form(step_id="discovery_confirm")

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle the user step."""
        if user_input is None:
            return self.async_show_form(step_id=SOURCE_USER, data_schema=CONFIG_SCHEMA)

        self._async_abort_entries_match(user_input)
        errors: dict[str, str] = {}
        hub = EheimDigitalHub(
            host=user_input[CONF_HOST],
            session=async_get_clientsession(self.hass),
            loop=self.hass.loop,
            main_device_added_event=self.main_device_added_event,
        )

        try:
            await hub.connect()

            async with asyncio.timeout(2):
                # This event gets triggered when the first message is received from
                # the device, it contains the data necessary to create the main device.
                # This removes the race condition where the main device is accessed
                # before the response from the device is parsed.
                await self.main_device_added_event.wait()
                if TYPE_CHECKING:
                    # At this point the main device is always set
                    assert isinstance(hub.main, EheimDigitalDevice)
                await self.async_set_unique_id(
                    hub.main.mac_address, raise_on_progress=False
                )
                await hub.close()
        except (ClientError, TimeoutError):
            errors["base"] = "cannot_connect"
        except Exception:  # noqa: BLE001
            errors["base"] = "unknown"
            LOGGER.exception("Unknown exception occurred")
        else:
            self._abort_if_unique_id_configured()
            return self.async_create_entry(data=user_input, title=user_input[CONF_HOST])
        return self.async_show_form(
            step_id=SOURCE_USER,
            data_schema=CONFIG_SCHEMA,
            errors=errors,
        )

    async def async_step_reconfigure(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reconfiguration of the config entry."""
        if user_input is None:
            return self.async_show_form(
                step_id=SOURCE_RECONFIGURE, data_schema=CONFIG_SCHEMA
            )

        self._async_abort_entries_match(user_input)
        errors: dict[str, str] = {}
        hub = EheimDigitalHub(
            host=user_input[CONF_HOST],
            session=async_get_clientsession(self.hass),
            loop=self.hass.loop,
            main_device_added_event=self.main_device_added_event,
        )

        try:
            await hub.connect()

            async with asyncio.timeout(2):
                # This event gets triggered when the first message is received from
                # the device, it contains the data necessary to create the main device.
                # This removes the race condition where the main device is accessed
                # before the response from the device is parsed.
                await self.main_device_added_event.wait()
                if TYPE_CHECKING:
                    # At this point the main device is always set
                    assert isinstance(hub.main, EheimDigitalDevice)
                await self.async_set_unique_id(hub.main.mac_address)
                await hub.close()
        except (ClientError, TimeoutError):
            errors["base"] = "cannot_connect"
        except Exception:  # noqa: BLE001
            errors["base"] = "unknown"
            LOGGER.exception("Unknown exception occurred")
        else:
            self._abort_if_unique_id_mismatch()
            return self.async_update_reload_and_abort(
                self._get_reconfigure_entry(),
                data_updates=user_input,
            )
        return self.async_show_form(
            step_id=SOURCE_RECONFIGURE,
            data_schema=CONFIG_SCHEMA,
            errors=errors,
        )
