"""Config flow for zcc integration."""

from __future__ import annotations

import logging
from typing import Any

import voluptuous as vol
from zcc import (
    ControlPoint,
    ControlPointCannotConnectError,
    ControlPointConnectionRefusedError,
    ControlPointDescription,
    ControlPointDiscoveryService,
    ControlPointError,
    ControlPointInvalidHostError,
    ControlPointTimeoutError,
)

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PORT
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.selector import (
    SelectOptionDict,
    SelectSelector,
    SelectSelectorConfig,
    SelectSelectorMode,
)

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 5003
STEP_MANUAL_DATA_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_HOST): str,
        vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
    }
)

SELECTED_HOST_AND_PORT = "selected_host_and_port"


class ZimiConfigFlow(ConfigFlow, domain=DOMAIN):
    """Handle a config flow for zcc."""

    api: ControlPoint = None
    api_descriptions: list[ControlPointDescription]
    data: dict[str, Any]

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle the initial auto-discovery step."""

        self.data = {}

        try:
            self.api_descriptions = await ControlPointDiscoveryService().discovers()
        except ControlPointError:
            # ControlPointError is expected if no zcc are found on LAN
            return await self.async_step_manual()

        if len(self.api_descriptions) == 1:
            self.data[CONF_HOST] = self.api_descriptions[0].host
            self.data[CONF_PORT] = self.api_descriptions[0].port
            await self.check_connection(self.data[CONF_HOST], self.data[CONF_PORT])
            return await self.create_entry()

        return await self.async_step_selection()

    async def async_step_selection(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle selection of zcc to configure if multiple are discovered."""

        errors: dict[str, str] | None = {}

        if user_input is not None:
            self.data[CONF_HOST] = user_input[SELECTED_HOST_AND_PORT].split(":")[0]
            self.data[CONF_PORT] = int(user_input[SELECTED_HOST_AND_PORT].split(":")[1])
            errors = await self.check_connection(
                self.data[CONF_HOST], self.data[CONF_PORT]
            )
            if not errors:
                return await self.create_entry()

        available_options = [
            SelectOptionDict(
                label=f"{description.host}:{description.port}",
                value=f"{description.host}:{description.port}",
            )
            for description in self.api_descriptions
        ]

        available_schema = vol.Schema(
            {
                vol.Required(
                    SELECTED_HOST_AND_PORT, default=available_options[0]["value"]
                ): SelectSelector(
                    SelectSelectorConfig(
                        options=available_options,
                        mode=SelectSelectorMode.DROPDOWN,
                    )
                )
            }
        )

        return self.async_show_form(
            step_id="selection", data_schema=available_schema, errors=errors
        )

    async def async_step_manual(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle manual configuration step if needed."""

        errors: dict[str, str] | None = {}

        if user_input is not None:
            self.data = {**self.data, **user_input}

            errors = await self.check_connection(
                self.data[CONF_HOST], self.data[CONF_PORT]
            )

            if not errors:
                return await self.create_entry()

        return self.async_show_form(
            step_id="manual",
            data_schema=self.add_suggested_values_to_schema(
                STEP_MANUAL_DATA_SCHEMA, self.data
            ),
            errors=errors,
        )

    async def check_connection(self, host: str, port: int) -> dict[str, str] | None:
        """Check connection to zcc.

        Stores mac and returns None if successful, otherwise returns error message.
        """

        try:
            result = await ControlPointDiscoveryService().validate_connection(
                self.data[CONF_HOST], self.data[CONF_PORT]
            )
        except ControlPointInvalidHostError:
            return {"base": "invalid_host"}
        except ControlPointConnectionRefusedError:
            return {"base": "connection_refused"}
        except ControlPointCannotConnectError:
            return {"base": "cannot_connect"}
        except ControlPointTimeoutError:
            return {"base": "timeout"}
        except Exception:
            _LOGGER.exception("Unexpected error")
            return {"base": "unknown"}

        self.data[CONF_MAC] = format_mac(result.mac)

        return None

    async def create_entry(self) -> ConfigFlowResult:
        """Create entry for zcc."""

        await self.async_set_unique_id(self.data[CONF_MAC])
        self._abort_if_unique_id_configured()
        return self.async_create_entry(
            title=f"ZIMI Controller ({self.data[CONF_HOST]}:{self.data[CONF_PORT]})",
            data=self.data,
        )
