"""Config flow for the Duco integration."""

import logging
from typing import Any, override

from duco_connectivity import DucoClient
from duco_connectivity.exceptions import DucoConnectionError, DucoError
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import DOMAIN
from .validation import UnsupportedBoardError, async_get_supported_board_info

_LOGGER = logging.getLogger(__name__)

STEP_USER_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_HOST): str,
    }
)


class DucoConfigFlow(ConfigFlow, domain=DOMAIN):
    """Config flow for Duco."""

    VERSION = 1
    MINOR_VERSION = 1

    _host: str
    _box_name: str

    @override
    async def async_step_dhcp(
        self, discovery_info: DhcpServiceInfo
    ) -> ConfigFlowResult:
        """Handle DHCP discovery."""
        await self.async_set_unique_id(format_mac(discovery_info.macaddress))
        self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})

        try:
            box_name, _ = await self._validate_input(discovery_info.ip)
        except UnsupportedBoardError:
            _LOGGER.debug(
                "Unsupported Duco board discovered via DHCP at %s", discovery_info.ip
            )
            return self.async_abort(reason="unsupported_board")
        except DucoConnectionError:
            return self.async_abort(reason="cannot_connect")
        except DucoError:
            _LOGGER.exception("Unexpected error discovering Duco box via DHCP")
            return self.async_abort(reason="unknown")

        self._host = discovery_info.ip
        self._box_name = box_name
        self.context["title_placeholders"] = {"name": box_name}

        return await self.async_step_discovery_confirm()

    @override
    async def async_step_zeroconf(
        self, discovery_info: ZeroconfServiceInfo
    ) -> ConfigFlowResult:
        """Handle zeroconf discovery."""
        try:
            box_name, mac = await self._validate_input(discovery_info.host)
        except UnsupportedBoardError:
            _LOGGER.debug(
                "Unsupported Duco board discovered via zeroconf at %s",
                discovery_info.host,
            )
            return self.async_abort(reason="unsupported_board")
        except DucoConnectionError:
            return self.async_abort(reason="cannot_connect")
        except DucoError:
            _LOGGER.exception("Unexpected error discovering Duco box via zeroconf")
            return self.async_abort(reason="unknown")

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

        self._host = discovery_info.host
        self._box_name = box_name
        self.context["title_placeholders"] = {"name": box_name}

        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._box_name,
                data={CONF_HOST: self._host},
            )

        self._set_confirm_only()
        return self.async_show_form(
            step_id="discovery_confirm",
            description_placeholders={"name": self._box_name},
        )

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

        if user_input is not None:
            try:
                box_name, mac = await self._validate_input(user_input[CONF_HOST])
            except UnsupportedBoardError:
                errors["base"] = "unsupported_board"
            except DucoConnectionError:
                errors["base"] = "cannot_connect"
            except DucoError:
                _LOGGER.exception("Unexpected error connecting to Duco box")
                errors["base"] = "unknown"
            else:
                await self.async_set_unique_id(format_mac(mac))
                self._abort_if_unique_id_mismatch()
                return self.async_update_reload_and_abort(
                    reconfigure_entry,
                    title=box_name,
                    data_updates={CONF_HOST: user_input[CONF_HOST]},
                )

        return self.async_show_form(
            step_id="reconfigure",
            data_schema=self.add_suggested_values_to_schema(
                STEP_USER_SCHEMA, reconfigure_entry.data
            ),
            errors=errors,
        )

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

        if user_input is not None:
            try:
                box_name, mac = await self._validate_input(user_input[CONF_HOST])
            except UnsupportedBoardError:
                errors["base"] = "unsupported_board"
            except DucoConnectionError:
                errors["base"] = "cannot_connect"
            except DucoError:
                _LOGGER.exception("Unexpected error connecting to Duco box")
                errors["base"] = "unknown"
            else:
                await self.async_set_unique_id(format_mac(mac), raise_on_progress=False)
                self._abort_if_unique_id_configured()

                return self.async_create_entry(
                    title=box_name,
                    data={CONF_HOST: user_input[CONF_HOST]},
                )

        return self.async_show_form(
            step_id="user",
            data_schema=STEP_USER_SCHEMA,
            errors=errors,
        )

    async def _validate_input(self, host: str) -> tuple[str, str]:
        """Validate the user input by connecting to the Duco box.

        Returns a tuple of (box_name, mac_address).
        """
        client = DucoClient(
            session=async_get_clientsession(self.hass),
            host=host,
        )
        board_info = await async_get_supported_board_info(client)
        lan_info = await client.async_get_lan_info()
        return board_info.box_name, lan_info.mac
