"""
1. User can enter login/pass from GUI
2. User can set login/pass in YAML
3. If the password requires updating, user need to configure another component
   with the same login.
4. Captcha will be requested if necessary
5. If authorization through YAML does not work, user can continue it through
   the GUI.
"""

import logging
from functools import lru_cache

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.aiohttp_client import async_create_clientsession

from .core.const import DOMAIN
from .core.yandex_quasar import YandexQuasar
from .core.yandex_session import LoginResponse, YandexSession

_LOGGER = logging.getLogger(__name__)


# noinspection PyUnusedLocal
class YandexStationFlowHandler(ConfigFlow, domain=DOMAIN):
    @property
    @lru_cache()
    def yandex(self):
        session = async_create_clientsession(self.hass)
        return YandexSession(session)

    async def async_step_import(self, data: dict):
        """Init by component setup. Forward YAML login/pass to auth."""
        await self.async_set_unique_id(data["username"])
        self._abort_if_unique_id_configured()

        if "x_token" in data:
            return self.async_create_entry(
                title=data["username"], data={"x_token": data["x_token"]}
            )

        else:
            return await self.async_step_auth(data)

    async def async_step_user(self, user_input=None):
        """Init by user via GUI"""
        if user_input is None:
            return self.async_show_form(
                step_id="user",
                data_schema=vol.Schema(
                    {
                        vol.Required("method", default="qr"): vol.In(
                            {
                                "qr": "QR-код",
                                "auth": "Пароль или одноразовый ключ",
                                "email": "Ссылка на E-mail",
                                "cookies": "Cookies",
                                "token": "Токен",
                            }
                        )
                    }
                ),
            )

        method = user_input["method"]
        if method == "qr":
            return self.async_show_form(
                step_id="qr",
                description_placeholders={"qr_url": await self.yandex.get_qr()},
            )

        if method == "auth":
            return self.async_show_form(
                step_id=method,
                data_schema=vol.Schema(
                    {
                        vol.Required("username"): str,
                        vol.Required("password"): str,
                    }
                ),
            )

        if method == "email":
            return self.async_show_form(
                step_id=method,
                data_schema=vol.Schema(
                    {
                        vol.Required("username"): str,
                    }
                ),
            )

        # cookies, token
        return self.async_show_form(
            step_id=method,
            data_schema=vol.Schema(
                {
                    vol.Required(method): str,
                }
            ),
        )

    async def async_step_qr(self, user_input):
        resp = await self.yandex.login_qr()
        if not resp:
            self.cur_step["errors"] = {"base": "unauthorised"}
            return self.cur_step
        return await self._check_yandex_response(resp)

    async def async_step_auth(self, user_input):
        """User submited username and password. Or YAML error."""
        resp = await self.yandex.login_username(user_input["username"])
        if resp.ok:
            resp = await self.yandex.login_password(user_input["password"])
        return await self._check_yandex_response(resp)

    async def async_step_email(self, user_input):
        resp = await self.yandex.login_username(user_input["username"])
        if not resp.magic_link_email:
            self.cur_step["errors"] = {"base": "email.unsupported"}
            return self.cur_step

        await self.yandex.get_letter()
        return self.async_show_form(
            step_id="email2", description_placeholders={"email": resp.magic_link_email}
        )

    async def async_step_email2(self, user_input):
        resp = await self.yandex.login_letter()
        if not resp:
            self.cur_step["errors"] = {"base": "unauthorised"}
            return self.cur_step

        return await self._check_yandex_response(resp)

    async def async_step_cookies(self, user_input):
        resp = await self.yandex.login_cookies(user_input["cookies"])
        return await self._check_yandex_response(resp)

    async def async_step_token(self, user_input):
        resp = await self.yandex.validate_token(user_input["token"])
        return await self._check_yandex_response(resp)

    async def async_step_captcha(self, user_input):
        """User submited captcha. Or YAML error."""
        if user_input is None:
            return self.cur_step

        ok = await self.yandex.login_captcha(user_input["captcha_answer"])
        if not ok:
            return self.cur_step

        return self.async_show_form(
            step_id="captcha2",
            data_schema=vol.Schema(
                {
                    vol.Required("password"): str,
                }
            ),
        )

    async def async_step_captcha2(self, user_input):
        resp = await self.yandex.login_password(user_input["password"])
        return await self._check_yandex_response(resp)

    async def _check_yandex_response(self, resp: LoginResponse):
        """Check Yandex response. Do not create entry for the same login. Show
        captcha form if captcha required. Show auth form with error if error.
        """
        if resp.ok:
            # set unique_id or return existing entry
            entry = await self.async_set_unique_id(resp.display_login)
            if entry:
                # update existing entry with same login
                self.hass.config_entries.async_update_entry(
                    entry, data={"x_token": resp.x_token}
                )
                return self.async_abort(reason="account_updated")

            else:
                # create new entry for new login
                return self.async_create_entry(
                    title=resp.display_login, data={"x_token": resp.x_token}
                )

        elif resp.error_captcha_required:
            _LOGGER.debug("Captcha required")
            return self.async_show_form(
                step_id="captcha",
                data_schema=vol.Schema(
                    {
                        vol.Required("captcha_answer"): str,
                    }
                ),
                description_placeholders={
                    "captcha_url": await self.yandex.get_captcha()
                },
            )

        elif resp.errors:
            _LOGGER.debug(f"Config error: {resp.error}")
            if self.cur_step:
                self.cur_step["errors"] = {"base": resp.error}
                return self.cur_step

        raise AbortFlow("not_implemented")

    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry):
        return OptionsFlowHandler()


class OptionsFlowHandler(OptionsFlow):
    @property
    def config_entry(self):
        return self.hass.config_entries.async_get_entry(self.handler)

    async def async_step_init(self, user_input: dict = None):
        if user_input:
            return self.async_create_entry(title="", data=user_input)

        quasar: YandexQuasar = self.hass.data[DOMAIN][self.config_entry.unique_id]
        devices = {i["id"]: device_name(i) for i in quasar.devices}

        # sort by names
        devices = dict(sorted(devices.items(), key=lambda x: x[1]))

        defaults = dict(self.config_entry.options)
        if include := defaults.get("include"):
            # filter only existing devices
            defaults["include"] = [i for i in include if i in devices]

        data = vol_schema({vol.Optional("include"): cv.multi_select(devices)}, defaults)
        return self.async_show_form(step_id="init", data_schema=data)


def vol_schema(schema: dict, defaults: dict | None) -> vol.Schema:
    if defaults:
        for key in schema:
            if (value := defaults.get(key.schema)) is not None:
                key.default = vol.default_factory(value)
    return vol.Schema(schema)


def device_name(device: dict) -> str:
    if room := device.get("room_name"):
        return f"{device['house_name']} - {room} - {device['name']}"
    return f"{device['house_name']} - {device['name']}"
