"""Config flow for the Cync integration."""

from __future__ import annotations

from collections.abc import Mapping
import logging
from typing import Any

from pycync import Auth
from pycync.exceptions import AuthFailedError, CyncError, TwoFactorRequiredError
import voluptuous as vol

from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import (
    CONF_AUTHORIZE_STRING,
    CONF_EXPIRES_AT,
    CONF_REFRESH_TOKEN,
    CONF_TWO_FACTOR_CODE,
    CONF_USER_ID,
    DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_EMAIL): str,
        vol.Required(CONF_PASSWORD): str,
    }
)

STEP_TWO_FACTOR_SCHEMA = vol.Schema({vol.Required(CONF_TWO_FACTOR_CODE): str})


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

    VERSION = 1

    cync_auth: Auth = None

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Attempt login with user credentials."""
        errors: dict[str, str] = {}

        if user_input:
            try:
                errors = await self._validate_credentials(user_input)
            except TwoFactorRequiredError:
                return await self.async_step_two_factor()

            if not errors:
                return await self._create_config_entry(self.cync_auth.username)

        return self.async_show_form(
            step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
        )

    async def async_step_two_factor(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Attempt login with the two factor auth code sent to the user."""
        errors: dict[str, str] = {}

        if user_input:
            errors = await self._validate_credentials(user_input)

            if not errors:
                return await self._create_config_entry(self.cync_auth.username)

            return self.async_show_form(
                step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
            )

        return self.async_show_form(
            step_id="two_factor", data_schema=STEP_TWO_FACTOR_SCHEMA, errors=errors
        )

    async def async_step_reauth(
        self, entry_data: Mapping[str, Any]
    ) -> ConfigFlowResult:
        """Perform reauth upon an API authentication error."""
        return await self.async_step_reauth_confirm()

    async def async_step_reauth_confirm(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Dialog that informs the user that reauth is required and prompts for their Cync credentials."""
        errors: dict[str, str] = {}

        reauth_entry = self._get_reauth_entry()

        if user_input:
            try:
                errors = await self._validate_credentials(user_input)
            except TwoFactorRequiredError:
                return await self.async_step_two_factor()

            if not errors:
                return await self._create_config_entry(self.cync_auth.username)

        return self.async_show_form(
            step_id="reauth_confirm",
            data_schema=STEP_USER_DATA_SCHEMA,
            errors=errors,
            description_placeholders={CONF_EMAIL: reauth_entry.title},
        )

    async def _validate_credentials(self, user_input: dict[str, Any]) -> dict[str, str]:
        """Attempt to log in with user email and password, and return the error dict."""
        errors: dict[str, str] = {}

        if not self.cync_auth:
            self.cync_auth = Auth(
                async_get_clientsession(self.hass),
                username=user_input[CONF_EMAIL],
                password=user_input[CONF_PASSWORD],
            )

        try:
            await self.cync_auth.login(user_input.get(CONF_TWO_FACTOR_CODE))
        except TwoFactorRequiredError:
            raise
        except AuthFailedError:
            errors["base"] = "invalid_auth"
        except CyncError:
            errors["base"] = "cannot_connect"
        except Exception:
            _LOGGER.exception("Unexpected exception")
            errors["base"] = "unknown"

        return errors

    async def _create_config_entry(self, user_email: str) -> ConfigFlowResult:
        """Create the Cync config entry using input user data."""

        cync_user = self.cync_auth.user
        await self.async_set_unique_id(str(cync_user.user_id))

        config_data = {
            CONF_USER_ID: cync_user.user_id,
            CONF_AUTHORIZE_STRING: cync_user.authorize,
            CONF_EXPIRES_AT: cync_user.expires_at,
            CONF_ACCESS_TOKEN: cync_user.access_token,
            CONF_REFRESH_TOKEN: cync_user.refresh_token,
        }

        if self.source == SOURCE_REAUTH:
            self._abort_if_unique_id_mismatch()
            return self.async_update_reload_and_abort(
                entry=self._get_reauth_entry(), title=user_email, data=config_data
            )

        self._abort_if_unique_id_configured()

        return self.async_create_entry(title=user_email, data=config_data)
