"""Config flow for the VegeHub integration."""

import logging
from typing import Any

from vegehub import VegeHub
import voluptuous as vol

from homeassistant.components.webhook import (
    async_generate_id as webhook_generate_id,
    async_generate_url as webhook_generate_url,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import (
    CONF_DEVICE,
    CONF_HOST,
    CONF_IP_ADDRESS,
    CONF_MAC,
    CONF_WEBHOOK_ID,
)
from homeassistant.helpers.service_info import zeroconf
from homeassistant.util.network import is_ip_address

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


class VegeHubConfigFlow(ConfigFlow, domain=DOMAIN):
    """Handle a config flow for VegeHub integration."""

    _hub: VegeHub
    _hostname: str
    webhook_id: str

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

        if user_input is not None:
            if not is_ip_address(user_input[CONF_IP_ADDRESS]):
                # User-supplied IP address is invalid.
                errors["base"] = "invalid_ip"

            if not errors:
                self._hub = VegeHub(user_input[CONF_IP_ADDRESS])
                self._hostname = self._hub.ip_address
                errors = await self._setup_device()
                if not errors:
                    # Proceed to create the config entry
                    return await self._create_entry()

        # Show the form to allow the user to manually enter the IP address
        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_IP_ADDRESS): str,
                }
            ),
            errors=errors,
        )

    async def async_step_zeroconf(
        self, discovery_info: zeroconf.ZeroconfServiceInfo
    ) -> ConfigFlowResult:
        """Handle zeroconf discovery."""

        # Extract the IP address from the zeroconf discovery info
        device_ip = discovery_info.host

        self._async_abort_entries_match({CONF_IP_ADDRESS: device_ip})

        self._hostname = discovery_info.hostname.removesuffix(".local.")
        config_url = f"http://{discovery_info.hostname[:-1]}:{discovery_info.port}"

        # Create a VegeHub object to interact with the device
        self._hub = VegeHub(device_ip)

        try:
            await self._hub.retrieve_mac_address(retries=2)
        except ConnectionError:
            return self.async_abort(reason="cannot_connect")
        except TimeoutError:
            return self.async_abort(reason="timeout_connect")

        if not self._hub.mac_address:
            return self.async_abort(reason="cannot_connect")

        # Check if this device already exists
        await self.async_set_unique_id(self._hub.mac_address)
        self._abort_if_unique_id_configured(
            updates={CONF_IP_ADDRESS: device_ip, CONF_HOST: self._hostname}
        )

        # Add title and configuration URL to the context so that the device discovery
        # tile has the correct title, and a "Visit Device" link available.
        self.context.update(
            {
                "title_placeholders": {"host": self._hostname + " (" + device_ip + ")"},
                "configuration_url": (config_url),
            }
        )

        # If the device is new, allow the user to continue setup
        return await self.async_step_zeroconf_confirm()

    async def async_step_zeroconf_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle user confirmation for a discovered device."""
        errors: dict[str, str] = {}
        if user_input is not None:
            errors = await self._setup_device()
            if not errors:
                return await self._create_entry()

        # Show the confirmation form
        self._set_confirm_only()
        return self.async_show_form(step_id="zeroconf_confirm", errors=errors)

    async def _setup_device(self) -> dict[str, str]:
        """Set up the VegeHub device."""
        errors: dict[str, str] = {}
        self.webhook_id = webhook_generate_id()
        webhook_url = webhook_generate_url(
            self.hass,
            self.webhook_id,
            allow_external=False,
            allow_ip=True,
        )

        # Send the webhook address to the hub as its server target.
        # This step can happen in the init, because that gets executed
        # every time Home Assistant starts up, and this step should
        # only happen in the initial setup of the VegeHub.
        try:
            await self._hub.setup("", webhook_url, retries=1)
        except ConnectionError:
            errors["base"] = "cannot_connect"
        except TimeoutError:
            errors["base"] = "timeout_connect"

        if not self._hub.mac_address:
            errors["base"] = "cannot_connect"

        return errors

    async def _create_entry(self) -> ConfigFlowResult:
        """Create a config entry for the device."""

        # Check if this device already exists
        await self.async_set_unique_id(self._hub.mac_address)
        self._abort_if_unique_id_configured()

        # Save Hub info to be used later when defining the VegeHub object
        info_data = {
            CONF_IP_ADDRESS: self._hub.ip_address,
            CONF_HOST: self._hostname,
            CONF_MAC: self._hub.mac_address,
            CONF_DEVICE: self._hub.info,
            CONF_WEBHOOK_ID: self.webhook_id,
        }

        # Create the config entry for the new device
        return self.async_create_entry(title=self._hostname, data=info_data)
