#include "LoginController.h"

#include "ConnectivityChecker.h"
#include "FatalEventHandler.h"
#include "MegaApplication.h"
#include "MessageDialogOpener.h"
#include "Platform.h"
#include "Preferences.h"
#include "QmlDialogManager.h"
#include "RequestListenerManager.h"
#include "ServiceUrls.h"
#include "StatsEventHandler.h"
#include "TextDecorator.h"

#include <QQmlContext>

LoginController::LoginController(QObject* parent):
    QObject{parent},
    mMegaApi(MegaSyncApp->getMegaApi()),
    mPreferences(Preferences::instance()),
    mGlobalListener(std::make_unique<mega::QTMegaGlobalListener>(MegaSyncApp->getMegaApi(), this)),
    mEmailError(false),
    mPasswordError(false),
    mProgress(0),
    mState(LOGGED_OUT),
    mNewAccount(false),
    mTriggerFatalErrorAfterFetchnodes(false),
    mForceOnboarding(false),
    mOnboardingShown(false)
{
    ListenerCallbacks lcInfo{
        this,
        std::bind(&LoginController::onRequestStart, this, std::placeholders::_1),
        std::bind(&LoginController::onRequestFinish, this, std::placeholders::_1, std::placeholders::_2),
        std::bind(&LoginController::onRequestUpdate, this, std::placeholders::_1)
    };

    mDelegateListener = RequestListenerManager::instance().registerAndGetListener(lcInfo);

    mMegaApi->addRequestListener(mDelegateListener.get());
    mMegaApi->addGlobalListener(mGlobalListener.get());
    mConnectivityTimer = new QTimer(this);
    mConnectivityTimer->setSingleShot(true);
    mConnectivityTimer->setInterval(static_cast<int>(Preferences::MAX_LOGIN_TIME_MS));
    connect(mConnectivityTimer, &QTimer::timeout, this, &LoginController::runConnectivityCheck);

    EphemeralCredentials credentials{mPreferences->getEphemeralCredentials()};
    if(!credentials.sessionId.isEmpty())
    {
        mMegaApi->resumeCreateAccount(credentials.sessionId.toUtf8().constData());
    }

    // Connect to FatalEventHandler signals
    auto fatalEventHandler = FatalEventHandler::instance();
    connect(fatalEventHandler.get(),
            &FatalEventHandler::requestForceOnBoarding,
            this,
            [this]()
            {
                mForceOnboarding = true;
            });

    // Plug into AppStates
    connect(this,
            &LoginController::requestAppState,
            AppState::instance().get(),
            &AppState::setAppState);
    connect(AppState::instance().get(),
            &AppState::appStateChanged,
            this,
            &LoginController::onAppStateChanged);
}

void LoginController::login(const QString& email, const QString& password)
{
    mMegaApi->login(email.toUtf8().constData(), password.toUtf8().constData());
}

void LoginController::createAccount(const QString& email, const QString& password,
                                const QString& name, const QString& lastName)
{
    mMegaApi->createAccount(email.toUtf8().constData(), password.toUtf8().constData(),
                             name.toUtf8().constData(), lastName.toUtf8().constData());
}

void LoginController::changeRegistrationEmail(const QString& email)
{
    QString fullName = QLatin1String("%1 %2").arg(mName, mLastName);
    mMegaApi->resendSignupLink(email.toUtf8().constData(), fullName.toUtf8().constData());
}

void LoginController::login2FA(const QString& pin)
{
    mMegaApi->multiFactorAuthLogin(mEmail.toUtf8().constData(), mPassword.toUtf8().constData(), pin.toUtf8().constData());
}

const QString& LoginController::getEmail() const
{
    return mEmail;
}

void LoginController::cancelLogin() const
{
    mMegaApi->localLogout();
}

void LoginController::cancelCreateAccount() const
{
    mMegaApi->cancelCreateAccount();
}

double LoginController::getProgress() const
{
    return mProgress;
}

LoginController::State LoginController::getState() const
{
    return mState;
}

void LoginController::setState(State state)
{
    if ((getState() == State::FETCHING_NODES || getState() == State::FETCHING_NODES_2FA) &&
        state == State::FETCH_NODES_FINISHED)
    {
        mOnboardingShown = false;
        // Should the onboarding be shown (without taking "force" into account)?
        auto showOnboarding =
            !mPreferences->isOneTimeActionUserDone(Preferences::ONE_TIME_ACTION_ONBOARDING_SHOWN) &&
            !(mPreferences->isFirstBackupDone() || mPreferences->isFirstSyncDone());

        if (mTriggerFatalErrorAfterFetchnodes)
        {
            // The user, if mTriggerFatalErrorAfterFetchnodes is set to true, is presented
            // with a dialog enjoining them to re-create new syncs/backups, and if the user accepts,
            // The onboarding dialog is shown. Avoid showing the warning dialog in the first
            // place if the onboarding dialog will be shown anyway.
            // Put app in NOMINAL state, which will not show the warning dialog,
            // or in FATAL_ERROR state, which will show the warning dialog.
            emit requestAppState(showOnboarding ? AppState::NOMINAL : AppState::FATAL_ERROR);
            // Return here because we are interrupting the nominal workflow, and requesting
            // app state change will call LoginController::setState again.
            return;
        }
        else if (mForceOnboarding || showOnboarding)
        {
            mOnboardingShown = true;
            QmlDialogManager::instance()->openOnboardingDialog(mForceOnboarding || showOnboarding);
            state = State::FETCH_NODES_FINISHED_ONBOARDING;
            mPreferences->setOneTimeActionUserDone(Preferences::ONE_TIME_ACTION_ONBOARDING_SHOWN,
                                                   true);
        }
    }
    if (getState() != state)
    {
        mega::MegaApi::log(mega::MegaApi::LOG_LEVEL_DEBUG,
                           QString::fromUtf8("LoginController switching state from: %1 to: %2")
                               .arg(QVariant::fromValue<State>(mState).toString(),
                                    QVariant::fromValue<State>(state).toString())
                               .toUtf8()
                               .constData());

        mState = state;
        emit stateChanged();

        if (state == State::FETCH_NODES_FINISHED)
        {
            onboardingFinished();

            if (AppState::instance()->getAppState() == AppState::RELOADING)
            {
                emit requestAppState(AppState::NOMINAL);
            }
        }
    }
}

bool LoginController::getEmailError() const
{
    return mEmailError;
}

const QString& LoginController::getEmailErrorMsg() const
{
    return mEmailErrorMsg;
}

void LoginController::setEmailError(bool error)
{
    if(error != mEmailError)
    {
        mEmailError = error;
        emit emailErrorChanged();
    }
}

void LoginController::setEmailErrorMsg(const QString& msg)
{
    if(msg != mEmailErrorMsg)
    {
        mEmailErrorMsg = msg;
        emit emailErrorMsgChanged();
    }
}

bool LoginController::getPasswordError() const
{
    return mPasswordError;
}

const QString& LoginController::getPasswordErrorMsg() const
{
    return mPasswordErrorMsg;
}

void LoginController::setPasswordError(bool error)
{
    if(error != mPasswordError)
    {
        mPasswordError = error;
        emit passwordErrorChanged();
    }
}

void LoginController::setPasswordErrorMsg(const QString& msg)
{
    if(msg != mPasswordErrorMsg)
    {
        mPasswordErrorMsg = msg;
        emit passwordErrorMsgChanged();
    }
}

const QString& LoginController::getCreateAccountErrorMsg() const
{
    return mCreateAccountErrorMsg;
}

void LoginController::setCreateAccountErrorMsg(const QString& msg)
{
    if(msg != mCreateAccountErrorMsg)
    {
        mCreateAccountErrorMsg = msg;
        emit createAccountErrorMsgChanged();
    }
}

void LoginController::processOnboardingClosed()
{
    if(getState() == LoginController::State::FETCH_NODES_FINISHED_ONBOARDING)
    {
        if (!Preferences::instance()->isFirstSyncDone() &&
            !Preferences::instance()->isFirstBackupDone() && !mForceOnboarding)
        {
            MegaSyncApp->getStatsEventHandler()->sendEvent(
                AppStatsEvents::EventType::ONBOARDING_CLOSED_WITHOUT_SETTING_SYNCS);
        }

        mForceOnboarding = false;
        setState(LoginController::State::FETCH_NODES_FINISHED);
    }
}

bool LoginController::isFetchNodesFinished() const
{
    return getState() >= LoginController::State::FETCH_NODES_FINISHED_ONBOARDING;
}

void LoginController::onRequestFinish(mega::MegaRequest* request, mega::MegaError* e)
{
    switch(request->getType())
    {
        case mega::MegaRequest::TYPE_LOGIN:
        {
            mConnectivityTimer->stop();
            MegaSyncApp->initLocalServer();
            if(e->getErrorCode() == mega::MegaError::API_OK)
            {
                mPreferences->setAccountStateInGeneral(Preferences::STATE_LOGGED_OK);
            }
            else if(e->getErrorCode() != mega::MegaError::API_EMFAREQUIRED)
            {
                if(request->getText())
                {
                    setState(LOGGING_IN_2FA_FAILED);
                }
                else
                {
                    setState(LOGGED_OUT);
                }
            }
            onLogin(request, e);
            break;
        }
        case mega::MegaRequest::TYPE_LOGOUT:
        {
            onLogout(request, e);
            break;
        }
        case mega::MegaRequest::TYPE_CREATE_ACCOUNT:
        {
            if(request->getParamType() == mega::MegaApi::RESUME_ACCOUNT)
            {
                onAccountCreationResume(request, e);
            }
            else if(request->getParamType() == mega::MegaApi::CANCEL_ACCOUNT)
            {
                onAccountCreationCancel(request, e);
            }
            else
            {
                onAccountCreation(request, e);
            }
            break;
        }
        case mega::MegaRequest::TYPE_SEND_SIGNUP_LINK:
        {
            onEmailChanged(request, e);
            break;
        }
        case mega::MegaRequest::TYPE_FETCH_NODES:
        {
            onFetchNodes(request, e);
            break;
        }
        case mega::MegaRequest::TYPE_WHY_AM_I_BLOCKED:
        {
            onWhyAmIBlocked(request, e);
            break;
        }
    }
}

void LoginController::onRequestUpdate(mega::MegaRequest* request)
{
    if (request->getType() == mega::MegaRequest::TYPE_FETCH_NODES)
    {
        if (request->getTotalBytes() > 0)
        {
            double total = static_cast<double>(request->getTotalBytes());
            double part = static_cast<double>(request->getTransferredBytes());
            double progress = part/total;

            mProgress = progress;
            emit progressChanged();
        }
    }
}

void LoginController::onRequestStart(mega::MegaRequest* request)
{
    switch(request->getType())
    {
    case mega::MegaRequest::TYPE_LOGIN:
    {
        mConnectivityTimer->start();
        if(request->getText())
        {
            setState(LOGGING_IN_2FA_VALIDATING);
        }
        else
        {
            setState(LOGGING_IN);
        }
        break;
    }
    case mega::MegaRequest::TYPE_CREATE_ACCOUNT:
    {
        if(request->getParamType() == mega::MegaApi::CREATE_ACCOUNT)
        {
            setState(CREATING_ACCOUNT);
        }
        break;
    }
    case mega::MegaRequest::TYPE_FETCH_NODES:
    {
        if(mState == LOGGING_IN_2FA_VALIDATING)
        {
            setState(FETCHING_NODES_2FA);
        }
        else
        {
            setState(FETCHING_NODES);
        }
        break;
    }
    }
}

void LoginController::onEvent(mega::MegaApi*, mega::MegaEvent* event)
{
    if(event->getType() == mega::MegaEvent::EVENT_CONFIRM_USER_EMAIL)
    {
        mNewAccount = true;
        setEmail(QString::fromLatin1(event->getText()));
        mPreferences->removeEphemeralCredentials();
        setState(EMAIL_CONFIRMED);
        emit emailConfirmed();
    }
    else if (event->getType() == mega::MegaEvent::EVENT_STORAGE)
    {
        if (!isFetchNodesFinished())//event arrived too soon, we will apply it later
        {
            eventPendingStorage.reset(event->copy());
        }
        else
        {
            eventPendingStorage.reset();
        }
    }
}

void LoginController::onLogin(mega::MegaRequest* request, mega::MegaError* e)
{
    if(e->getErrorCode() == mega::MegaError::API_OK)
    {
        mPreferences->setEmailAndGeneralSettings(QString::fromUtf8(request->getEmail()));

        dumpSession();

        fetchNodes();
        if (!mPreferences->hasLoggedIn())
        {
            mPreferences->setHasLoggedIn(QDateTime::currentSecsSinceEpoch());
        }
        MegaSyncApp->onLoginFinished();
    }
    else
    {
        mPreferences->setAccountStateInGeneral(Preferences::STATE_LOGGED_FAILED);
        switch(e->getErrorCode())
        {
            case mega::MegaError::API_EINCOMPLETE:
            {
                setPasswordErrorMsg(tr("Please check your e-mail and click the link to confirm your account."));
                break;
            }
            case mega::MegaError::API_ETOOMANY:
            {
                setPasswordErrorMsg(tr("You have attempted to log in too many times.[BR]Please wait until %1 and try again.")
                                    .replace(QString::fromUtf8("[BR]"), QString::fromUtf8("\n"))
                                    .arg(QTime::currentTime().addSecs(3600).toString(QString::fromUtf8("hh:mm"))));
                break;
            }
            case mega::MegaError::API_EMFAREQUIRED:
            {
                mPassword = QString::fromUtf8(request->getPassword());
                setEmail(QString::fromLatin1(request->getEmail()));
                setState(LOGGING_IN_2FA_REQUIRED);
                break;
            }
            case mega::MegaError::API_ENOENT:
            {
                setPasswordErrorMsg(tr("Invalid email or password. Please try again."));
                break;
            }
            case mega::MegaError::API_EACCESS:
            {
                break;
            }
            default:
            {
                setPasswordErrorMsg(QCoreApplication::translate("MegaError", e->getErrorString()));
                break;
            }
        }
    }

    MegaSyncApp->onGlobalSyncStateChanged(mMegaApi);

    if(e->getErrorCode() != mega::MegaError::API_EMFAREQUIRED)
    {
        setEmailError(e->getErrorCode() != mega::MegaError::API_OK);
        setPasswordError(e->getErrorCode() != mega::MegaError::API_OK);
    }
}

void LoginController::onboardingFinished()
{
    SyncInfo::instance()->onboardingFinished(mOnboardingShown);
    MegaSyncApp->onboardingFinished(false, mOnboardingShown);
}

void LoginController::onAccountCreation(mega::MegaRequest* request, mega::MegaError* e)
{
    if(e->getErrorCode() == mega::MegaError::API_OK)
    {
        setEmail(QString::fromLatin1(request->getEmail()));
        mName = QString::fromUtf8(request->getName());
        mLastName = QString::fromUtf8(request->getText());
        EphemeralCredentials credentials;
        credentials.email = mEmail;
        credentials.sessionId = QString::fromUtf8(request->getSessionKey());
        mPreferences->setEphemeralCredentials(credentials);
        MegaSyncApp->getStatsEventHandler()->sendEvent(AppStatsEvents::EventType::ACC_CREATION_START);
        if (!mPreferences->accountCreationTime())
        {
            mPreferences->setAccountCreationTime(QDateTime::currentSecsSinceEpoch());
        }
        setState(WAITING_EMAIL_CONFIRMATION);
    }
    else
    {
        setState(CREATING_ACCOUNT_FAILED);
        setCreateAccountErrorMsg(getRepeatedEmailMsg());
    }
}

void LoginController::onAccountCreationResume(mega::MegaRequest* request, mega::MegaError* e)
{
    Q_UNUSED(request)
    if(e->getErrorCode() == mega::MegaError::API_OK)
    {
        EphemeralCredentials credentials = mPreferences->getEphemeralCredentials();
        setEmail(credentials.email);
        setState(WAITING_EMAIL_CONFIRMATION);
    }
    else
    {
        mPreferences->removeEphemeralCredentials();
        setState(LOGGED_OUT);
    }
}

void LoginController::onEmailChanged(mega::MegaRequest* request, mega::MegaError* e)
{
    if(e->getErrorCode() != mega::MegaError::API_OK)
    {
        emit changeRegistrationEmailFinished(false, getRepeatedEmailMsg());
    }
    else
    {
        setEmail(QString::fromLatin1(request->getEmail()));
        EphemeralCredentials credentials = mPreferences->getEphemeralCredentials();
        credentials.email = mEmail;
        mPreferences->setEphemeralCredentials(credentials);
        emit changeRegistrationEmailFinished(true);
    }
}

void LoginController::onFetchNodes(mega::MegaRequest* request, mega::MegaError* e)
{
    Q_UNUSED(request)
    if (e->getErrorCode() == mega::MegaError::API_OK)
    {
        //Update/set root node
        MegaSyncApp->getRootNode(true); //TODO: move this to thread pool, notice that mRootNode is used below
        MegaSyncApp->getVaultNode(true);
        MegaSyncApp->getRubbishNode(true);

        mPreferences->setAccountStateInGeneral(Preferences::STATE_FETCHNODES_OK);
        mPreferences->setNeedsFetchNodesInGeneral(false);

        mProgress = 0; //sets guestdialog progressbar as indeterminate
        emit progressChanged();
        MegaSyncApp->onFetchNodesFinished();
    }
    else
    {
        setState(LOGGED_OUT);
        mPreferences->setAccountStateInGeneral(Preferences::STATE_FETCHNODES_FAILED);
        mPreferences->setNeedsFetchNodesInGeneral(true);
        mega::MegaApi::log(mega::MegaApi::LOG_LEVEL_ERROR, QString::fromUtf8("Error fetching nodes: %1")
                                                                .arg(QString::fromUtf8(e->getErrorString())).toUtf8().constData());
    }

    if(e->getErrorCode() == mega::MegaError::API_OK)
    {
        setState(FETCH_NODES_FINISHED);

        if(eventPendingStorage)
        {
            MegaSyncApp->onEvent(mMegaApi, eventPendingStorage.get());
            eventPendingStorage.reset();
        }
    }
}

void LoginController::onWhyAmIBlocked(mega::MegaRequest* request, mega::MegaError* e)
{
    if (e->getErrorCode() == mega::MegaError::API_OK
        && request->getNumber() == mega::MegaApi::ACCOUNT_NOT_BLOCKED)
    {
        mega::MegaApi::log(
            mega::MegaApi::LOG_LEVEL_DEBUG,
            QString::fromUtf8("onWhyAmIBlocked recovery check: state=%1 !getRootNode()=%2")
                .arg(QVariant::fromValue<State>(mState).toString())
                .arg(!MegaSyncApp->getRootNode())
                .toUtf8()
                .constData());

        // If the original login was interrupted by the block, drive the state machine
        // back to FETCH_NODES_FINISHED. Checking getRootNode() is not reliable because
        // the SDK hydrates nodes from local cache even when the in-session fetchNodes
        // failed (state ended up in LOGGED_OUT but getRootNode() still returns non-null).
        if (mState != FETCHING_NODES && mState != FETCHING_NODES_2FA &&
            mState != FETCH_NODES_FINISHED && mState != FETCH_NODES_FINISHED_ONBOARDING)
        {
            fetchNodes();
        }
    }
}

void LoginController::onAccountCreationCancel(mega::MegaRequest* request, mega::MegaError* e)
{
    Q_UNUSED(request)
    Q_UNUSED(e)
    mMegaApi->logout(false, nullptr); //megaapi->cancelCreateAccount doesn´t invalidate the ephemeral session.
    mPreferences->removeEphemeralCredentials();
    mEmail.clear();
    mPassword.clear();
    mName.clear();
    mLastName.clear();
    emit emailChanged();
    emit accountCreationCancelled();
}

void LoginController::onLogout(mega::MegaRequest* request, mega::MegaError* e)
{
    Q_UNUSED(e)
    Q_UNUSED(request)
    int errorCode = e->getErrorCode();
    int paramType =  request->getParamType();
    if (errorCode == mega::MegaError::API_EINCOMPLETE && paramType == mega::MegaError::API_ESSL)
    {
        return;
    }

    setState(LOGGED_OUT);
}

void LoginController::fetchNodes()
{
    assert(mState != FETCHING_NODES);

    mMegaApi->fetchNodes();
}

void LoginController::dumpSession()
{
    std::unique_ptr<char []> session(mMegaApi->dumpSession());
    if (session)
    {
        mPreferences->setSession(QString::fromUtf8(session.get()));
    }
}

QString LoginController::getRepeatedEmailMsg()
{
    return tr("Email address already in use.");
}

void LoginController::setEmail(const QString& email)
{
    if(mEmail != email)
    {
        mEmail = email;
        emit emailChanged();
    }
}

void LoginController::runConnectivityCheck()
{
    QNetworkProxy proxy;
    proxy.setType(QNetworkProxy::NoProxy);
    if (mPreferences->proxyType() == Preferences::PROXY_TYPE_CUSTOM)
    {
        int proxyProtocol = mPreferences->proxyProtocol();
        switch (proxyProtocol)
        {
            case Preferences::PROXY_PROTOCOL_SOCKS5H:
                proxy.setType(QNetworkProxy::Socks5Proxy);
                break;
            default:
                proxy.setType(QNetworkProxy::HttpProxy);
                break;
        }

        proxy.setHostName(mPreferences->proxyServer());
        proxy.setPort(quint16(mPreferences->proxyPort()));
        if (mPreferences->proxyRequiresAuth())
        {
            proxy.setUser(mPreferences->getProxyUsername());
            proxy.setPassword(mPreferences->getProxyPassword());
        }
    }
    else if (mPreferences->proxyType() == mega::MegaProxy::PROXY_AUTO)
    {
        std::unique_ptr<mega::MegaProxy> autoProxy(mMegaApi->getAutoProxySettings());
        if (autoProxy && autoProxy->getProxyType() == mega::MegaProxy::PROXY_CUSTOM)
        {
            QString proxyURL = QString::fromUtf8(autoProxy->getProxyURL());

            QStringList parts = proxyURL.split(QString::fromUtf8("://"));
            if (parts.size() == 2 && parts[0].startsWith(QString::fromUtf8("socks")))
            {
                proxy.setType(QNetworkProxy::Socks5Proxy);
            }
            else
            {
                proxy.setType(QNetworkProxy::HttpProxy);
            }

            QStringList arguments = parts[parts.size()-1].split(QString::fromUtf8(":"));
            if (arguments.size() == 2)
            {
                proxy.setHostName(arguments[0]);
                proxy.setPort(quint16(arguments[1].toInt()));
            }
        }
    }

    ConnectivityChecker* connectivityChecker =
        new ConnectivityChecker(ServiceUrls::getProxyTestUrl());
    connectivityChecker->setProxy(proxy);
    connectivityChecker->setTestString(Preferences::PROXY_TEST_SUBSTRING);
    connectivityChecker->setTimeout(Preferences::PROXY_TEST_TIMEOUT_MS);

    connect(connectivityChecker, &ConnectivityChecker::testFinished, this,
             &LoginController::onConnectivityCheckFinished, Qt::UniqueConnection);

    connectivityChecker->startCheck();
    mega::MegaApi::log(mega::MegaApi::LOG_LEVEL_INFO, "Running connectivity test...");
}


void LoginController::onConnectivityCheckFinished(bool success)
{
    sender()->deleteLater();
    if(success)
    {
        mega::MegaApi::log(mega::MegaApi::LOG_LEVEL_INFO, "Connectivity test finished OK");
    }
    else
    {
        MegaSyncApp->showErrorMessage(tr("MEGAsync is unable to connect. Please check your "
                                           "Internet connectivity and local firewall configuration. "
                                           "Note that most antivirus software includes a firewall."));
    }
}

void LoginController::onAppStateChanged(AppState::AppStates oldAppState,
                                        AppState::AppStates newAppState)
{
    // We are here only interrested by the RELOADING state
    if (newAppState == AppState::RELOADING)
    {
        // If previous state was a FATAL ERROR, we force a fetchNodes()
        if (oldAppState == AppState::FATAL_ERROR)
        {
            fetchNodes();
        }
        // Otherwise, the fetchnodes has been started by the SDK, and we just need to monitor it
        else
        {
            setState(FETCHING_NODES);
        }
    }
    // Transition to FATAL_ERROR_PENDING_FETCHNODES, which can happen when jsacd attribute has been
    // re-generated and a warning message can be shown to the user.
    // However the message has to be shown after fetchnodes, so we need to defer processing.
    else if (newAppState == AppState::FATAL_ERROR_PENDING_FETCHNODES)
    {
        mTriggerFatalErrorAfterFetchnodes = true;
    }
    // Transition from FATAL_ERROR or FATAL_ERROR_PENDING_FETCHNODES to nominal:
    // Only act when the app went through FATAL_ERROR_PENDING_FETCHNODES state (this is known by
    // checking the value of  mTriggerFatalErrorAfterFetchnodes).
    else if ((oldAppState == AppState::FATAL_ERROR ||
              oldAppState == AppState::FATAL_ERROR_PENDING_FETCHNODES) &&
             newAppState == AppState::NOMINAL)
    {
        if (mTriggerFatalErrorAfterFetchnodes)
        {
            mTriggerFatalErrorAfterFetchnodes = false;

            setState(FETCH_NODES_FINISHED);
        }
    }
}

FastLoginController::FastLoginController(QObject *parent)
    : LoginController(parent)
{

}

bool FastLoginController::fastLogin()
{
    const auto session = mPreferences->getSession();
    const bool hasSession = !session.isEmpty();
    if (hasSession)
    {
        mMegaApi->fastLogin(session.toUtf8().constData());
    }
    return hasSession;
}

void FastLoginController::onLogin(mega::MegaRequest* request, mega::MegaError* e)
{
    Q_UNUSED(request)
    //This prevents to handle logins in the initial setup wizard
    if (mPreferences->logged())
    {
        Platform::getInstance()->prepareForSync();
        int errorCode = e->getErrorCode();
        if (errorCode == mega::MegaError::API_OK)
        {
            MegaSyncApp->onLoginFinished();
            if (!mPreferences->getSession().isEmpty())
            {
                //Successful login, fetch nodes
                fetchNodes();
                return;
            }
        }
        else if (errorCode != mega::MegaError::API_ESID && errorCode != mega::MegaError::API_ESSL)
        //Invalid session or public key, already managed in TYPE_LOGOUT
        {
            MessageDialogInfo msgInfo;
            msgInfo.descriptionText =
                tr("Login error: %1")
                    .arg(QCoreApplication::translate("MegaError", e->getErrorString()));

            MessageDialogOpener::warning(msgInfo);
        }

        //Wrong login -> logout
        MegaSyncApp->unlink(true);
    }
    MegaSyncApp->onGlobalSyncStateChanged(mMegaApi);
}

void FastLoginController::onboardingFinished()
{
    MegaSyncApp->onboardingFinished(true);
}

LogoutController::LogoutController(QObject* parent)
    : QObject(parent)
      , mMegaApi(MegaSyncApp->getMegaApi())
      , mLoginInWithoutSession(false)
{   
    ListenerCallbacks lcInfo{
        this,
        std::bind(&LogoutController::onRequestStart, this, std::placeholders::_1),
        std::bind(&LogoutController::onRequestFinish, this, std::placeholders::_1, std::placeholders::_2)
    };

    mDelegateListener = RequestListenerManager::instance().registerAndGetListener(lcInfo);
    mMegaApi->addRequestListener(mDelegateListener.get());
}

LogoutController::~LogoutController()
{
}

void LogoutController::onRequestFinish(mega::MegaRequest* request, mega::MegaError* e)
{
    if(request->getType() == mega::MegaRequest::TYPE_LOGIN)
    {
        mLoginInWithoutSession = false;
    }

    if(request->getType() != mega::MegaRequest::TYPE_LOGOUT)
    {
        return;
    }
    int errorCode = e->getErrorCode();
    int paramType =  request->getParamType();
    if (errorCode || paramType)
    {
        if (errorCode == mega::MegaError::API_EINCOMPLETE && paramType == mega::MegaError::API_ESSL && !mLoginInWithoutSession)
        {
            //Typical case: Connecting from a public wifi when the wifi sends you to a landing page
            //SDK cannot connect through SSL securely and asks MEGA Desktop to log out

                   //In previous versions, the user was asked to continue with a warning about a MITM risk.
                   //One of the options was disabling the public key pinning to continue working as usual
                   //This option was to risky and the solution taken was silently retry reconnection

                   // Retry while enforcing key pinning silently
            mMegaApi->retryPendingConnections();
            return;
        }

        if (paramType == mega::MegaError::API_ESID)
        {
            MessageDialogInfo msgInfo;
            msgInfo.descriptionText =
                tr("You have been logged out on this computer from another location");

            MessageDialogOpener::information(msgInfo);
        }
        else if (paramType == mega::MegaError::API_ESSL)
        {
            MessageDialogInfo msgInfo;

            msgInfo.textFormat = Qt::RichText;

            msgInfo.titleText = tr("Secure connection failed");
            msgInfo.descriptionText =
                tr("MEGA couldn’t connect securely.[BR]If you’re on public Wi-Fi, try signing in "
                   "through your browser first. Your ISP or security software might also be "
                   "interfering with the connection.[BR][A]Learn more[/A]");
            Text::RichText(ServiceUrls::getSslErrorHelpUrl().toString())
                .process(msgInfo.descriptionText);

            msgInfo.buttons = QMessageBox::Ok;

            MessageDialogOpener::critical(msgInfo);
            mMegaApi->localLogout();
        }
        else if (paramType != mega::MegaError::API_EACCESS && paramType != mega::MegaError::API_EBLOCKED)
        {
            MessageDialogInfo msgInfo;
            if(errorCode != mega::MegaError::API_OK)
            {
                msgInfo.descriptionText =
                    tr("You have been logged out because of this error: %1")
                        .arg(QCoreApplication::translate("MegaError", e->getErrorString()));
            }
            else
            {
                QString text = tr("You have been logged out. Please contact [A]Support[/A] "
                                  "if this issue persists.");
                Text::Link link(ServiceUrls::instance()->getSupportFormUrl().toString());
                link.process(text);
                msgInfo.descriptionText = text;
                msgInfo.textFormat = Qt::RichText;
            }

            MessageDialogOpener::information(msgInfo);
        }
        MegaSyncApp->unlink();
    }

    // Check for any sync disabled by logout to warn user on next login with user&password
    const auto syncSettings (SyncInfo::instance()->getAllSyncSettings());
    auto isErrorLoggedOut = [](const std::shared_ptr<SyncSettings> s) {return s->getError() == mega::MegaSync::LOGGED_OUT;};
    if (std::any_of(syncSettings.cbegin(), syncSettings.cend(), isErrorLoggedOut))
    {
        Preferences::instance()->setNotifyDisabledSyncsOnLogin(true);
    }

    emit logout(!request->getFlag());
    mLoginInWithoutSession = false;
}

void LogoutController::onRequestStart(mega::MegaRequest *request)
{
    if(request->getType() == mega::MegaRequest::TYPE_LOGIN && !request->getSessionKey())
    {
        mLoginInWithoutSession = true;
    }
}
