"""Config flow for slide_local integration."""

from __future__ import annotations

import logging
from typing import Any

from goslideapi.goslideapi import (
    AuthenticationFailed,
    ClientConnectionError,
    ClientTimeoutError,
    DigestAuthCalcError,
    GoSlideLocal as SlideLocalApi,
)
import voluptuous as vol

from homeassistant.config_entries import (
    ConfigFlow,
    ConfigFlowResult,
    OptionsFlowWithReload,
)
from homeassistant.const import CONF_API_VERSION, CONF_HOST, CONF_MAC, CONF_PASSWORD
from homeassistant.core import callback
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import CONF_INVERT_POSITION, DOMAIN
from .coordinator import SlideConfigEntry

_LOGGER = logging.getLogger(__name__)


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

    _mac: str = ""
    _host: str = ""
    _api_version: int | None = None

    VERSION = 1
    MINOR_VERSION = 1

    @staticmethod
    @callback
    def async_get_options_flow(
        config_entry: SlideConfigEntry,
    ) -> SlideOptionsFlowHandler:
        """Get the options flow for this handler."""
        return SlideOptionsFlowHandler()

    async def async_test_connection(
        self, user_input: dict[str, str | int]
    ) -> dict[str, str]:
        """Reusable Auth Helper."""
        slide = SlideLocalApi()

        # first test, if API version 2 is working
        await slide.slide_add(
            user_input[CONF_HOST],
            user_input.get(CONF_PASSWORD, ""),
            2,
        )

        try:
            result = await slide.slide_info(user_input[CONF_HOST])
        except ClientConnectionError, ClientTimeoutError:
            return {"base": "cannot_connect"}
        except AuthenticationFailed, DigestAuthCalcError:
            return {"base": "invalid_auth"}
        except Exception:
            _LOGGER.exception("Exception occurred during connection test")
            return {"base": "unknown"}

        if result is not None:
            self._api_version = 2
            self._mac = format_mac(result["mac"])
            return {}

        # API version 2 is not working, try API version 1 instead
        await slide.slide_add(
            user_input[CONF_HOST],
            user_input.get(CONF_PASSWORD, ""),
            1,
        )

        try:
            result = await slide.slide_info(user_input[CONF_HOST])
        except ClientConnectionError, ClientTimeoutError:
            return {"base": "cannot_connect"}
        except AuthenticationFailed, DigestAuthCalcError:
            return {"base": "invalid_auth"}
        except Exception:
            _LOGGER.exception("Exception occurred during connection test")
            return {"base": "unknown"}

        if result is None:
            # API version 1 isn't working either
            return {"base": "unknown"}

        self._api_version = 1
        self._mac = format_mac(result["mac"])

        return {}

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle the user step."""
        errors: dict[str, str] = {}
        if user_input is not None:
            if not (errors := await self.async_test_connection(user_input)):
                await self.async_set_unique_id(self._mac)
                self._abort_if_unique_id_configured()
                user_input |= {
                    CONF_MAC: self._mac,
                    CONF_API_VERSION: self._api_version,
                }

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

        if user_input is not None and user_input.get(CONF_HOST) is not None:
            self._host = user_input[CONF_HOST]

        return self.async_show_form(
            step_id="user",
            data_schema=self.add_suggested_values_to_schema(
                vol.Schema(
                    {
                        vol.Required(CONF_HOST): str,
                        vol.Optional(CONF_PASSWORD): str,
                    }
                ),
                {CONF_HOST: self._host},
            ),
            errors=errors,
        )

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

        if user_input is not None:
            if not (errors := await self.async_test_connection(user_input)):
                await self.async_set_unique_id(self._mac)
                self._abort_if_unique_id_mismatch(
                    description_placeholders={CONF_MAC: self._mac}
                )
                user_input |= {
                    CONF_API_VERSION: self._api_version,
                }

                return self.async_update_reload_and_abort(
                    self._get_reconfigure_entry(),
                    data_updates=user_input,
                )

        entry: SlideConfigEntry = self._get_reconfigure_entry()

        return self.async_show_form(
            step_id="reconfigure",
            data_schema=self.add_suggested_values_to_schema(
                vol.Schema(
                    {
                        vol.Required(CONF_HOST): str,
                    }
                ),
                {
                    CONF_HOST: entry.data[CONF_HOST],
                    CONF_PASSWORD: entry.data.get(CONF_PASSWORD, ""),
                },
            ),
            errors=errors,
        )

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

        # id is in the format 'slide_000000000000'
        self._mac = format_mac(str(discovery_info.properties.get("id"))[6:])

        await self.async_set_unique_id(self._mac)

        ip = str(discovery_info.ip_address)
        _LOGGER.debug("Slide device discovered, ip %s", ip)

        self._abort_if_unique_id_configured({CONF_HOST: ip}, reload_on_update=True)

        errors = {}
        if errors := await self.async_test_connection(
            {
                CONF_HOST: ip,
            }
        ):
            return self.async_abort(
                reason="discovery_connection_failed",
                description_placeholders={
                    "error": errors["base"],
                },
            )

        self._host = ip

        return await self.async_step_zeroconf_confirm()

    async def async_step_zeroconf_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Confirm discovery."""

        if user_input is not None:
            user_input |= {
                CONF_HOST: self._host,
                CONF_API_VERSION: 2,
                CONF_MAC: format_mac(self._mac),
            }
            return self.async_create_entry(
                title=user_input[CONF_HOST],
                data=user_input,
                options={CONF_INVERT_POSITION: False},
            )

        self._set_confirm_only()
        return self.async_show_form(
            step_id="zeroconf_confirm",
            description_placeholders={
                "host": self._host,
            },
        )


class SlideOptionsFlowHandler(OptionsFlowWithReload):
    """Handle a options flow for slide_local."""

    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Manage the options."""
        if user_input is not None:
            return self.async_create_entry(data=user_input)

        return self.async_show_form(
            step_id="init",
            data_schema=self.add_suggested_values_to_schema(
                vol.Schema(
                    {
                        vol.Required(CONF_INVERT_POSITION): bool,
                    }
                ),
                {CONF_INVERT_POSITION: self.config_entry.options[CONF_INVERT_POSITION]},
            ),
        )
