// SPDX-License-Identifier: GPL-3.0-only
/*
 *  PolyMC - Minecraft Launcher
 *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, version 3.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 *      Copyright 2013-2021 MultiMC Contributors
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */

#include "ProfileSetupDialog.h"
#include "ui_ProfileSetupDialog.h"

#include <QPushButton>
#include <QAction>
#include <QRegularExpressionValidator>
#include <QJsonDocument>
#include <QDebug>

#include "ui/dialogs/ProgressDialog.h"

#include <Application.h>
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"


ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget *parent)
    : QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
{
    ui->setupUi(this);
    ui->errorLabel->setVisible(false);

    goodIcon = APPLICATION->getThemedIcon("status-good");
    yellowIcon = APPLICATION->getThemedIcon("status-yellow");
    badIcon = APPLICATION->getThemedIcon("status-bad");

    QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}");
    auto nameEdit = ui->nameEdit;
    nameEdit->setValidator(new QRegularExpressionValidator(permittedNames));
    nameEdit->setClearButtonEnabled(true);
    validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition);
    connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited);

    checkStartTimer.setSingleShot(true);
    connect(&checkStartTimer, &QTimer::timeout, this, &ProfileSetupDialog::startCheck);

    setNameStatus(NameStatus::NotSet, QString());
}

ProfileSetupDialog::~ProfileSetupDialog()
{
    delete ui;
}

void ProfileSetupDialog::on_buttonBox_accepted()
{
    setupProfile(currentCheck);
}

void ProfileSetupDialog::on_buttonBox_rejected()
{
    reject();
}

void ProfileSetupDialog::setNameStatus(ProfileSetupDialog::NameStatus status, QString errorString = QString())
{
    nameStatus = status;
    auto okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
    switch(nameStatus)
    {
        case NameStatus::Available: {
            validityAction->setIcon(goodIcon);
            okButton->setEnabled(true);
        }
        break;
        case NameStatus::NotSet:
        case NameStatus::Pending:
            validityAction->setIcon(yellowIcon);
            okButton->setEnabled(false);
            break;
        case NameStatus::Exists:
        case NameStatus::Error:
            validityAction->setIcon(badIcon);
            okButton->setEnabled(false);
            break;
    }
    if(!errorString.isEmpty()) {
        ui->errorLabel->setText(errorString);
        ui->errorLabel->setVisible(true);
    }
    else {
        ui->errorLabel->setVisible(false);
    }
}

void ProfileSetupDialog::nameEdited(const QString& name)
{
    if(!ui->nameEdit->hasAcceptableInput()) {
        setNameStatus(NameStatus::NotSet, tr("Name is too short - must be between 3 and 16 characters long."));
        return;
    }
    scheduleCheck(name);
}

void ProfileSetupDialog::scheduleCheck(const QString& name) {
    queuedCheck = name;
    setNameStatus(NameStatus::Pending);
    checkStartTimer.start(1000);
}

void ProfileSetupDialog::startCheck() {
    if(isChecking) {
        return;
    }
    if(queuedCheck.isNull()) {
        return;
    }
    checkName(queuedCheck);
}


void ProfileSetupDialog::checkName(const QString &name) {
    if(isChecking) {
        return;
    }

    currentCheck = name;
    isChecking = true;

    auto token = m_accountToSetup->accessToken();

    auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
    QNetworkRequest request = QNetworkRequest(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setRawHeader("Accept", "application/json");
    request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());

    AuthRequest *requestor = new AuthRequest(this);
    connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
    requestor->get(request);
}

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

    if(error == QNetworkReply::NoError) {
        auto doc = QJsonDocument::fromJson(data);
        auto root = doc.object();
        auto statusValue = root.value("status").toString("INVALID");
        if(statusValue == "AVAILABLE") {
            setNameStatus(NameStatus::Available);
        }
        else if (statusValue == "DUPLICATE") {
            setNameStatus(NameStatus::Exists, tr("Minecraft profile with name %1 already exists.").arg(currentCheck));
        }
        else if (statusValue == "NOT_ALLOWED") {
            setNameStatus(NameStatus::Exists, tr("The name %1 is not allowed.").arg(currentCheck));
        }
        else {
            setNameStatus(NameStatus::Error, tr("Unhandled profile name status: %1").arg(statusValue));
        }
    }
    else {
        setNameStatus(NameStatus::Error, tr("Failed to check name availability."));
    }
    isChecking = false;
}

void ProfileSetupDialog::setupProfile(const QString &profileName) {
    if(isWorking) {
        return;
    }

    auto token = m_accountToSetup->accessToken();

    auto url = QString("https://api.minecraftservices.com/minecraft/profile");
    QNetworkRequest request = QNetworkRequest(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setRawHeader("Accept", "application/json");
    request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());

    QString payloadTemplate("{\"profileName\":\"%1\"}");
    auto data = payloadTemplate.arg(profileName).toUtf8();

    AuthRequest *requestor = new AuthRequest(this);
    connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
    requestor->post(request, data);
    isWorking = true;

    auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
    button->setEnabled(false);
}

namespace {

struct MojangError{
    static MojangError fromJSON(QByteArray data) {
        MojangError out;
        out.error = QString::fromUtf8(data);
        auto doc = QJsonDocument::fromJson(data, &out.parseError);
        auto object = doc.object();

        out.fullyParsed = true;
        out.fullyParsed &= Parsers::getString(object.value("path"), out.path);
        out.fullyParsed &= Parsers::getString(object.value("error"), out.error);
        out.fullyParsed &= Parsers::getString(object.value("errorMessage"), out.errorMessage);

        return out;
    }

    QString rawError;
    QJsonParseError parseError;
    bool fullyParsed;

    QString path;
    QString error;
    QString errorMessage;
};

}

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

    isWorking = false;
    if(error == QNetworkReply::NoError) {
        /*
         * data contains the profile in the response
         * ... we could parse it and update the account, but let's just return back to the normal login flow instead...
         */
        accept();
    }
    else {
        auto parsedError = MojangError::fromJSON(data);
        ui->errorLabel->setVisible(true);
        ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
        qDebug() << parsedError.rawError;
        auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
        button->setEnabled(true);
    }
}
