"""Config flow for Nanoleaf integration."""
from __future__ import annotations

import logging
import os
from typing import Any, Final, cast

from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components import ssdp, zeroconf
from homeassistant.const import CONF_HOST, CONF_TOKEN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

# For discovery integration import
CONFIG_FILE: Final = ".nanoleaf.conf"

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


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Nanoleaf config flow."""

    reauth_entry: config_entries.ConfigEntry | None = None

    VERSION = 1

    def __init__(self) -> None:
        """Initialize a Nanoleaf flow."""
        self.nanoleaf: Nanoleaf

        # For discovery integration import
        self.discovery_conf: dict
        self.device_id: str

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle Nanoleaf flow initiated by the user."""
        if user_input is None:
            return self.async_show_form(
                step_id="user", data_schema=USER_SCHEMA, last_step=False
            )
        self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
        self.nanoleaf = Nanoleaf(
            async_get_clientsession(self.hass), user_input[CONF_HOST]
        )
        try:
            await self.nanoleaf.authorize()
        except Unavailable:
            return self.async_show_form(
                step_id="user",
                data_schema=USER_SCHEMA,
                errors={"base": "cannot_connect"},
                last_step=False,
            )
        except Unauthorized:
            pass
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception("Unknown error connecting to Nanoleaf")
            return self.async_show_form(
                step_id="user",
                data_schema=USER_SCHEMA,
                last_step=False,
                errors={"base": "unknown"},
            )
        return await self.async_step_link()

    async def async_step_reauth(self, data: dict[str, str]) -> FlowResult:
        """Handle Nanoleaf reauth flow if token is invalid."""
        self.reauth_entry = cast(
            config_entries.ConfigEntry,
            self.hass.config_entries.async_get_entry(self.context["entry_id"]),
        )
        self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), data[CONF_HOST])
        self.context["title_placeholders"] = {"name": self.reauth_entry.title}
        return await self.async_step_link()

    async def async_step_zeroconf(
        self, discovery_info: zeroconf.ZeroconfServiceInfo
    ) -> FlowResult:
        """Handle Nanoleaf Zeroconf discovery."""
        _LOGGER.debug("Zeroconf discovered: %s", discovery_info)
        return await self._async_homekit_zeroconf_discovery_handler(discovery_info)

    async def async_step_homekit(
        self, discovery_info: zeroconf.ZeroconfServiceInfo
    ) -> FlowResult:
        """Handle Nanoleaf Homekit discovery."""
        _LOGGER.debug("Homekit discovered: %s", discovery_info)
        return await self._async_homekit_zeroconf_discovery_handler(discovery_info)

    async def _async_homekit_zeroconf_discovery_handler(
        self, discovery_info: zeroconf.ZeroconfServiceInfo
    ) -> FlowResult:
        """Handle Nanoleaf Homekit and Zeroconf discovery."""
        return await self._async_discovery_handler(
            discovery_info.host,
            discovery_info.name.replace(f".{discovery_info.type}", ""),
            discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID],
        )

    async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
        """Handle Nanoleaf SSDP discovery."""
        _LOGGER.debug("SSDP discovered: %s", discovery_info)
        return await self._async_discovery_handler(
            discovery_info.ssdp_headers["_host"],
            discovery_info.ssdp_headers["nl-devicename"],
            discovery_info.ssdp_headers["nl-deviceid"],
        )

    async def _async_discovery_handler(
        self, host: str, name: str, device_id: str
    ) -> FlowResult:
        """Handle Nanoleaf discovery."""
        # The name is unique and printed on the device and cannot be changed.
        await self.async_set_unique_id(name)
        self._abort_if_unique_id_configured({CONF_HOST: host})

        # Import from discovery integration
        self.device_id = device_id
        self.discovery_conf = cast(
            dict,
            await self.hass.async_add_executor_job(
                load_json, self.hass.config.path(CONFIG_FILE)
            ),
        )
        auth_token: str | None = self.discovery_conf.get(self.device_id, {}).get(
            "token",  # >= 2021.4
            self.discovery_conf.get(host, {}).get("token"),  # < 2021.4
        )
        if auth_token is not None:
            self.nanoleaf = Nanoleaf(
                async_get_clientsession(self.hass), host, auth_token
            )
            _LOGGER.warning(
                "Importing Nanoleaf %s from the discovery integration", name
            )
            return await self.async_setup_finish(discovery_integration_import=True)
        self.nanoleaf = Nanoleaf(async_get_clientsession(self.hass), host)
        self.context["title_placeholders"] = {"name": name}
        return await self.async_step_link()

    async def async_step_link(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle Nanoleaf link step."""
        if user_input is None:
            return self.async_show_form(step_id="link")

        try:
            await self.nanoleaf.authorize()
        except Unauthorized:
            return self.async_show_form(
                step_id="link", errors={"base": "not_allowing_new_tokens"}
            )
        except Unavailable:
            return self.async_abort(reason="cannot_connect")
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception("Unknown error authorizing Nanoleaf")
            return self.async_show_form(step_id="link", errors={"base": "unknown"})

        if self.reauth_entry is not None:
            self.hass.config_entries.async_update_entry(
                self.reauth_entry,
                data={
                    **self.reauth_entry.data,
                    CONF_TOKEN: self.nanoleaf.auth_token,
                },
            )
            await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
            return self.async_abort(reason="reauth_successful")

        return await self.async_setup_finish()

    async def async_setup_finish(
        self, discovery_integration_import: bool = False
    ) -> FlowResult:
        """Finish Nanoleaf config flow."""
        try:
            await self.nanoleaf.get_info()
        except Unavailable:
            return self.async_abort(reason="cannot_connect")
        except InvalidToken:
            return self.async_abort(reason="invalid_token")
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception(
                "Unknown error connecting with Nanoleaf at %s", self.nanoleaf.host
            )
            return self.async_abort(reason="unknown")
        name = self.nanoleaf.name

        await self.async_set_unique_id(name)
        self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})

        if discovery_integration_import:
            if self.nanoleaf.host in self.discovery_conf:
                self.discovery_conf.pop(self.nanoleaf.host)
            if self.device_id in self.discovery_conf:
                self.discovery_conf.pop(self.device_id)
            _LOGGER.info(
                "Successfully imported Nanoleaf %s from the discovery integration",
                name,
            )
            if self.discovery_conf:
                await self.hass.async_add_executor_job(
                    save_json, self.hass.config.path(CONFIG_FILE), self.discovery_conf
                )
            else:
                await self.hass.async_add_executor_job(
                    os.remove, self.hass.config.path(CONFIG_FILE)
                )

        return self.async_create_entry(
            title=name,
            data={
                CONF_HOST: self.nanoleaf.host,
                CONF_TOKEN: self.nanoleaf.auth_token,
            },
        )
