import getpass
import os
import sys
from pathlib import Path
from typing import Optional

from clarabot_nano_common.common.config.constants import CommonConfigConstants
from clarabot_nano_common.common.log import LoggingBootstrap
from clarabot_nano_common.config.handler import ConfigLockedByRunningInstance
from clarabot_nano_common.utility.network import create_session

from clarabot_nano.common.auth.handler import ServerAuthenticator
from clarabot_nano.common.auth.two_factor import AbstractTwoFactorErrorHandler, TwoFactorError
from clarabot_nano.core.config.handler import Config


class ConsoleTwoFactorHandler(AbstractTwoFactorErrorHandler):
    def _query_user_2fa_code(self) -> Optional[str]:
        return input("Enter two-factor authentication code: ")


async def _bootstrap_login(authenticator: ServerAuthenticator):
    if authenticator.could_try_resume():
        print("Previous session found, checking if it is still valid...")
        try:
            await authenticator.resume(timeout=10.0)
            print("Current session is still valid!")
        except Exception as e:
            print("Resuming the session has failed:", e)
    else:
        print("No previous session found.")

    print("")
    print("To change the account or create a new session enter new credentials.")
    print("Supplying empty credentials will delete the currently stored session by logout.")
    print("If you want to exit, signal interrupt. (CTRL^C)")

    email = input("Email: ")
    if not email:
        print("WARNING: If the session is shared among many Nano instances all of them will be forcibly logged out!")
        confirm = input('To confirm deleting the current session type "logout": ')
        if confirm.lower() == "logout":
            await authenticator.logout(timeout=10.0)
            print("Session deleted!")
        print("Confirmation failed!")
    else:
        password = getpass.getpass("Password: ")
        print("Attempting login...")
        two_factor_handler = ConsoleTwoFactorHandler()
        server_session_key = os.urandom(16)
        otp_salt = None
        tfa_code = None
        while True:
            try:
                await authenticator.login(
                    email,
                    password,
                    server_session_key=server_session_key,
                    timeout=10.0,
                    otp_salt=otp_salt,
                    tfa_code=tfa_code,
                )
                break
            except TwoFactorError as e:
                if tfa := two_factor_handler.get_two_factor(e.message):
                    extra_args = two_factor_handler.handle_two_factor_response(*tfa)
                    if "otpSalt" in extra_args:
                        otp_salt = extra_args["otpSalt"]
                    if "2faCode" in extra_args:
                        tfa_code = extra_args["2faCode"]
                else:
                    raise e
        print("Login successful, new session created!")


async def core_bootstrap_login(
    local_config,
    bootstrap_config_dir: Optional[str] = None,
    bootstrap_data_dir: Optional[str] = None,
):
    """
    A small command-line utility to perform the only action that is impossible to perform remotely or by editing the
    configuration files of the Nano instance: Login
    """
    try:
        with Config.lock_and_load(local_config, bootstrap_config_dir, bootstrap_data_dir) as config:
            LoggingBootstrap.setup_logging(config.log_config, "core-bootstrap-login")
            async with create_session(**CommonConfigConstants.AIOHTTP_CLIENT_SESSION_OPTS) as session:
                authenticator = ServerAuthenticator(
                    session,
                    CommonConfigConstants.SERVER_DOMAIN,
                    CommonConfigConstants.SERVER_PATH,
                    Path(config.local["data_dir"]),
                )
                await _bootstrap_login(authenticator)
    except KeyboardInterrupt:
        pass
    except ConfigLockedByRunningInstance:
        print("The Nano instance must be stopped to perform bootstrap.")
        sys.exit(3)
