"""Config flow to configure the Peblar integration."""

from collections.abc import Mapping
from typing import Any, override

from aiohttp import CookieJar
from peblar import Peblar, PeblarAuthenticationError, PeblarConnectionError
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.selector import (
    TextSelector,
    TextSelectorConfig,
    TextSelectorType,
)
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import DOMAIN, LOGGER


class PeblarFlowHandler(ConfigFlow, domain=DOMAIN):
    """Handle a Peblar config flow."""

    VERSION = 1

    _discovery_info: ZeroconfServiceInfo

    @override
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle a flow initiated by the user."""
        errors = {}

        if user_input is not None:
            peblar = Peblar(
                host=user_input[CONF_HOST],
                session=async_create_clientsession(
                    self.hass, cookie_jar=CookieJar(unsafe=True)
                ),
            )
            try:
                await peblar.login(password=user_input[CONF_PASSWORD])
                info = await peblar.system_information()
            except PeblarAuthenticationError:
                errors[CONF_PASSWORD] = "invalid_auth"
            except PeblarConnectionError:
                errors[CONF_HOST] = "cannot_connect"
            except Exception:  # noqa: BLE001
                LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"
            else:
                await self.async_set_unique_id(
                    info.product_serial_number, raise_on_progress=False
                )
                self._abort_if_unique_id_configured()
                return self.async_create_entry(title="Peblar", data=user_input)
        else:
            user_input = {}

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(
                {
                    vol.Required(
                        CONF_HOST, default=user_input.get(CONF_HOST)
                    ): TextSelector(TextSelectorConfig(autocomplete="off")),
                    vol.Required(CONF_PASSWORD): TextSelector(
                        TextSelectorConfig(type=TextSelectorType.PASSWORD)
                    ),
                }
            ),
            errors=errors,
        )

    async def async_step_reconfigure(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reconfiguration of a Peblar device."""
        errors = {}
        reconfigure_entry = self._get_reconfigure_entry()

        if user_input is not None:
            peblar = Peblar(
                host=user_input[CONF_HOST],
                session=async_create_clientsession(
                    self.hass, cookie_jar=CookieJar(unsafe=True)
                ),
            )
            try:
                await peblar.login(password=user_input[CONF_PASSWORD])
                info = await peblar.system_information()
            except PeblarAuthenticationError:
                errors[CONF_PASSWORD] = "invalid_auth"
            except PeblarConnectionError:
                errors[CONF_HOST] = "cannot_connect"
            except Exception:  # noqa: BLE001
                LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"
            else:
                await self.async_set_unique_id(info.product_serial_number)
                self._abort_if_unique_id_mismatch(reason="different_device")
                return self.async_update_reload_and_abort(
                    reconfigure_entry,
                    data_updates=user_input,
                )

        host = reconfigure_entry.data[CONF_HOST]
        if user_input is not None:
            host = user_input[CONF_HOST]

        return self.async_show_form(
            step_id="reconfigure",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_HOST, default=host): TextSelector(
                        TextSelectorConfig(autocomplete="off")
                    ),
                    vol.Required(CONF_PASSWORD): TextSelector(
                        TextSelectorConfig(type=TextSelectorType.PASSWORD)
                    ),
                }
            ),
            errors=errors,
        )

    @override
    async def async_step_zeroconf(
        self, discovery_info: ZeroconfServiceInfo
    ) -> ConfigFlowResult:
        """Handle zeroconf discovery of a Peblar device."""
        if not (sn := discovery_info.properties.get("sn")):
            return self.async_abort(reason="no_serial_number")

        await self.async_set_unique_id(sn)
        self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host})

        self._discovery_info = discovery_info
        self.context.update(
            {
                "title_placeholders": {
                    "name": discovery_info.name.replace("._http._tcp.local.", "")
                },
                "configuration_url": f"http://{discovery_info.host}",
            },
        )
        return await self.async_step_zeroconf_confirm()

    async def async_step_zeroconf_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle a flow initiated by zeroconf."""
        errors = {}

        if user_input is not None:
            peblar = Peblar(
                host=self._discovery_info.host,
                session=async_create_clientsession(
                    self.hass, cookie_jar=CookieJar(unsafe=True)
                ),
            )
            try:
                await peblar.login(password=user_input[CONF_PASSWORD])
            except PeblarAuthenticationError:
                errors[CONF_PASSWORD] = "invalid_auth"
            except PeblarConnectionError:
                errors["base"] = "cannot_connect"
            except Exception:  # noqa: BLE001
                LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"
            else:
                return self.async_create_entry(
                    title="Peblar",
                    data={
                        CONF_HOST: self._discovery_info.host,
                        CONF_PASSWORD: user_input[CONF_PASSWORD],
                    },
                )

        return self.async_show_form(
            step_id="zeroconf_confirm",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_PASSWORD): TextSelector(
                        TextSelectorConfig(type=TextSelectorType.PASSWORD)
                    ),
                }
            ),
            description_placeholders={
                "hostname": self._discovery_info.name.replace("._http._tcp.local.", ""),
                "host": self._discovery_info.host,
            },
            errors=errors,
        )

    async def async_step_reauth(
        self, entry_data: Mapping[str, Any]
    ) -> ConfigFlowResult:
        """Handle initiation of re-authentication with a Peblar device."""
        return await self.async_step_reauth_confirm()

    async def async_step_reauth_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle re-authentication with a Peblar device."""
        errors = {}

        if user_input is not None:
            reauth_entry = self._get_reauth_entry()
            peblar = Peblar(
                host=reauth_entry.data[CONF_HOST],
                session=async_create_clientsession(
                    self.hass, cookie_jar=CookieJar(unsafe=True)
                ),
            )
            try:
                await peblar.login(password=user_input[CONF_PASSWORD])
            except PeblarAuthenticationError:
                errors[CONF_PASSWORD] = "invalid_auth"
            except PeblarConnectionError:
                errors["base"] = "cannot_connect"
            except Exception:  # noqa: BLE001
                LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"
            else:
                return self.async_update_reload_and_abort(
                    reauth_entry,
                    data={
                        CONF_HOST: reauth_entry.data[CONF_HOST],
                        CONF_PASSWORD: user_input[CONF_PASSWORD],
                    },
                )

        return self.async_show_form(
            step_id="reauth_confirm",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_PASSWORD): TextSelector(
                        TextSelectorConfig(type=TextSelectorType.PASSWORD)
                    ),
                }
            ),
            errors=errors,
        )
