"""Config flow for Qbus."""

import logging
from typing import TYPE_CHECKING, Any, override

from qbusmqttapi.discovery import QbusMqttDevice
from qbusmqttapi.factory import QbusMqttMessageFactory, QbusMqttTopicFactory

from homeassistant.components.mqtt import client as mqtt
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_ID
from homeassistant.helpers.service_info.mqtt import MqttServiceInfo

from .const import CONF_SERIAL_NUMBER, DOMAIN
from .coordinator import QbusConfigCoordinator

_LOGGER = logging.getLogger(__name__)


class QbusFlowHandler(ConfigFlow, domain=DOMAIN):
    """Handle Qbus config flow."""

    VERSION = 1

    def __init__(self) -> None:
        """Initialize."""
        self._message_factory = QbusMqttMessageFactory()
        self._topic_factory = QbusMqttTopicFactory()

        self._gateway_topic = self._topic_factory.get_gateway_state_topic()
        self._config_topic = self._topic_factory.get_config_topic()
        self._device_topic = self._topic_factory.get_device_state_topic("+")

        self._device: QbusMqttDevice | None = None

    @override
    async def async_step_mqtt(
        self, discovery_info: MqttServiceInfo
    ) -> ConfigFlowResult:
        """Handle a flow initialized by MQTT discovery."""
        _LOGGER.debug("Running mqtt discovery for topic %s", discovery_info.topic)

        # Abort if the payload is empty
        if not discovery_info.payload:
            _LOGGER.debug("Payload empty")
            return self.async_abort(reason="invalid_discovery_info")

        match discovery_info.subscribed_topic:
            case self._gateway_topic:
                return await self._async_handle_gateway_topic(discovery_info)

            case self._config_topic:
                return await self._async_handle_config_topic(discovery_info)

            case self._device_topic:
                return await self._async_handle_device_topic(discovery_info)

        return self.async_abort(reason="invalid_discovery_info")

    async def async_step_discovery_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Confirm the setup."""
        if TYPE_CHECKING:
            assert self._device is not None

        if user_input is not None:
            return self.async_create_entry(
                title=f"Controller {self._device.serial_number}",
                data={
                    CONF_SERIAL_NUMBER: self._device.serial_number,
                    CONF_ID: self._device.id,
                },
            )

        return self.async_show_form(
            step_id="discovery_confirm",
            description_placeholders={
                CONF_SERIAL_NUMBER: self._device.serial_number,
            },
        )

    @override
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle a flow initialized by the user."""
        return self.async_abort(reason="not_supported")

    async def _async_handle_gateway_topic(
        self, discovery_info: MqttServiceInfo
    ) -> ConfigFlowResult:
        _LOGGER.debug("Handling gateway state")
        gateway_state = self._message_factory.parse_gateway_state(
            discovery_info.payload
        )

        if gateway_state is not None and gateway_state.online is True:
            _LOGGER.debug("Requesting config")
            await mqtt.async_publish(
                self.hass, self._topic_factory.get_get_config_topic(), b""
            )

        # Abort to wait for config topic
        return self.async_abort(reason="discovery_in_progress")

    async def _async_handle_config_topic(
        self, discovery_info: MqttServiceInfo
    ) -> ConfigFlowResult:
        _LOGGER.debug("Handling config topic")
        qbus_config = self._message_factory.parse_discovery(discovery_info.payload)

        if qbus_config is not None:
            QbusConfigCoordinator.get_or_create(self.hass).store_config(qbus_config)

            _LOGGER.debug("Requesting device states")
            device_ids = [x.id for x in qbus_config.devices]
            request = self._message_factory.create_state_request(device_ids)
            await mqtt.async_publish(self.hass, request.topic, request.payload)

        # Abort to wait for device topic
        return self.async_abort(reason="discovery_in_progress")

    async def _async_handle_device_topic(
        self, discovery_info: MqttServiceInfo
    ) -> ConfigFlowResult:
        _LOGGER.debug("Discovering device")
        qbus_config = await QbusConfigCoordinator.get_or_create(
            self.hass
        ).async_get_or_request_config()

        if qbus_config is None:
            _LOGGER.error("Qbus config not ready")
            return self.async_abort(reason="invalid_discovery_info")

        device_id = discovery_info.topic.split("/")[2]
        self._device = qbus_config.get_device_by_id(device_id)

        if self._device is None:
            _LOGGER.warning("Device with id '%s' not found in config", device_id)
            return self.async_abort(reason="invalid_discovery_info")

        await self.async_set_unique_id(self._device.serial_number)

        # Do not use error message "already_configured" (which is the
        # default), as this will result in unsubscribing from the triggered
        # mqtt topic. The topic subscribed to has a wildcard to allow
        # discovery of multiple devices. Unsubscribing would result in
        # not discovering new or unconfigured devices.
        self._abort_if_unique_id_configured(error="device_already_configured")

        self.context.update(
            {
                "title_placeholders": {
                    CONF_SERIAL_NUMBER: self._device.serial_number,
                }
            }
        )

        return await self.async_step_discovery_confirm()
