"""Config flow for flume integration."""

from collections.abc import Mapping
import logging
import os
from typing import Any, override

from pyflume import FlumeAuth, FlumeDeviceList
from requests.exceptions import RequestException
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import (
    CONF_CLIENT_ID,
    CONF_CLIENT_SECRET,
    CONF_PASSWORD,
    CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .const import BASE_TOKEN_FILENAME, DOMAIN

_LOGGER = logging.getLogger(__name__)

# If flume ever implements a login page for oauth
# we can use the oauth2 support built into Home Assistant.
#
# Currently they only implement the token endpoint
#
DATA_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_USERNAME): str,
        vol.Required(CONF_PASSWORD): str,
        vol.Required(CONF_CLIENT_ID): str,
        vol.Required(CONF_CLIENT_SECRET): str,
    }
)


def _validate_input(
    hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool
) -> FlumeDeviceList:
    """Validate in the executor."""
    flume_token_full_path = hass.config.path(
        f"{BASE_TOKEN_FILENAME}-{data[CONF_USERNAME]}"
    )
    if clear_token_file and os.path.exists(flume_token_full_path):
        os.unlink(flume_token_full_path)

    return FlumeDeviceList(
        FlumeAuth(
            data[CONF_USERNAME],
            data[CONF_PASSWORD],
            data[CONF_CLIENT_ID],
            data[CONF_CLIENT_SECRET],
            flume_token_file=flume_token_full_path,
        )
    )


async def validate_input(
    hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool = False
) -> dict[str, Any]:
    """Validate the user input allows us to connect.

    Data has the keys from DATA_SCHEMA with values provided by the user.
    """
    try:
        flume_devices = await hass.async_add_executor_job(
            _validate_input, hass, data, clear_token_file
        )
    except RequestException as err:
        raise CannotConnect from err
    except Exception as err:
        _LOGGER.exception("Auth exception")
        raise InvalidAuth from err
    if not flume_devices or not flume_devices.device_list:
        raise CannotConnect

    # Return info that you want to store in the config entry.
    return {"title": data[CONF_USERNAME]}


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

    VERSION = 1

    def __init__(self) -> None:
        """Init flume config flow."""
        self._reauth_unique_id: str | None = None

    @override
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle the initial step."""
        errors: dict[str, str] = {}
        if user_input is not None:
            await self.async_set_unique_id(user_input[CONF_USERNAME])
            self._abort_if_unique_id_configured()

            try:
                info = await validate_input(self.hass, user_input)
                return self.async_create_entry(title=info["title"], data=user_input)
            except CannotConnect:
                errors["base"] = "cannot_connect"
            except InvalidAuth:
                errors[CONF_PASSWORD] = "invalid_auth"

        return self.async_show_form(
            step_id="user",
            data_schema=DATA_SCHEMA,
            errors=errors,
            description_placeholders={
                "api_url": "https://portal.flumetech.com/settings#token"
            },
        )

    async def async_step_reauth(
        self, entry_data: Mapping[str, Any]
    ) -> ConfigFlowResult:
        """Handle reauth."""
        self._reauth_unique_id = self.context["unique_id"]
        return await self.async_step_reauth_confirm()

    async def async_step_reauth_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reauth input."""
        errors: dict[str, str] = {}
        existing_entry = await self.async_set_unique_id(self._reauth_unique_id)
        assert existing_entry
        if user_input is not None:
            new_data = {**existing_entry.data, CONF_PASSWORD: user_input[CONF_PASSWORD]}
            try:
                await validate_input(self.hass, new_data, clear_token_file=True)
            except CannotConnect:
                errors["base"] = "cannot_connect"
            except InvalidAuth:
                errors[CONF_PASSWORD] = "invalid_auth"
            else:
                self.hass.config_entries.async_update_entry(
                    existing_entry, data=new_data
                )
                await self.hass.config_entries.async_reload(existing_entry.entry_id)
                return self.async_abort(reason="reauth_successful")

        return self.async_show_form(
            description_placeholders={
                CONF_USERNAME: existing_entry.data[CONF_USERNAME]
            },
            step_id="reauth_confirm",
            data_schema=vol.Schema(
                {
                    vol.Required(CONF_PASSWORD): str,
                }
            ),
            errors=errors,
        )


class CannotConnect(HomeAssistantError):
    """Error to indicate we cannot connect."""


class InvalidAuth(HomeAssistantError):
    """Error to indicate there is invalid auth."""
