#include "XboxAuthorizationStep.h"

#include <QJsonDocument>
#include <QJsonParseError>
#include <QNetworkRequest>

#include "Logging.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h"

XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
    : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{}

XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default;

QString XboxAuthorizationStep::describe()
{
    return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
}

void XboxAuthorizationStep::rehydrate()
{
    // FIXME: check if the tokens are good?
}

void XboxAuthorizationStep::perform()
{
    QString xbox_auth_template = R"XXX(
{
    "Properties": {
        "SandboxId": "RETAIL",
        "UserTokens": [
            "%1"
        ]
    },
    "RelyingParty": "%2",
    "TokenType": "JWT"
}
)XXX";
    auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
    // http://xboxlive.com
    QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setRawHeader("Accept", "application/json");
    AuthRequest* requestor = new AuthRequest(this);
    connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone);
    requestor->post(request, xbox_auth_data.toUtf8());
    qDebug() << "Getting authorization token for " << m_relyingParty;
}

void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
    auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
    requestor->deleteLater();

    qCDebug(authCredentials()) << data;
    if (error != QNetworkReply::NoError) {
        qWarning() << "Reply error:" << error;
        if (Net::isApplicationError(error)) {
            if (!processSTSError(error, data, headers)) {
                emit finished(AccountTaskState::STATE_FAILED_SOFT,
                              tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
            } else {
                emit finished(AccountTaskState::STATE_FAILED_SOFT,
                              tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
            }
        } else {
            emit finished(AccountTaskState::STATE_OFFLINE,
                          tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
        }
        return;
    }

    Katabasis::Token temp;
    if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
        emit finished(AccountTaskState::STATE_FAILED_SOFT,
                      tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
        return;
    }

    if (temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
        emit finished(AccountTaskState::STATE_FAILED_SOFT,
                      tr("Server has changed %1 authorization user hash in the reply. Something is wrong.").arg(m_authorizationKind));
        return;
    }
    auto& token = *m_token;
    token = temp;

    emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
}

bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
{
    if (error == QNetworkReply::AuthenticationRequiredError) {
        QJsonParseError jsonError;
        QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
        if (jsonError.error) {
            qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
            emit finished(AccountTaskState::STATE_FAILED_SOFT,
                          tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString()));
            return true;
        }

        int64_t errorCode = -1;
        auto obj = doc.object();
        if (!Parsers::getNumber(obj.value("XErr"), errorCode)) {
            emit finished(AccountTaskState::STATE_FAILED_SOFT,
                          tr("XErr element is missing from %1 authorization error response.").arg(m_authorizationKind));
            return true;
        }
        switch (errorCode) {
            case 2148916233: {
                emit finished(AccountTaskState::STATE_FAILED_SOFT,
                              tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
                                  .arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>"));
                return true;
            }
            case 2148916235: {
                // NOTE: this is the Grulovia error
                emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox Live is not available in your country. You've been blocked."));
                return true;
            }
            case 2148916238: {
                emit finished(
                    AccountTaskState::STATE_FAILED_SOFT,
                    tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
                        .arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>"));
                return true;
            }
            default: {
                emit finished(AccountTaskState::STATE_FAILED_SOFT,
                              tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode));
                return true;
            }
        }
    }
    return false;
}