// SPDX-FileCopyrightText: 2023 Filipe Azevedo <pasnox@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

// Qt
#include <QCoreApplication>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocale>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QThread>
#include <QTimer>
#include <QUrl>
#include <QUrlQuery>

template<typename T>
T keyValue(const QJsonObject &object, const QStringView &key);

template<>
QString keyValue(const QJsonObject &object, const QStringView &key)
{
    return object.value(key).toString();
}

template<>
int keyValue(const QJsonObject &object, const QStringView &key)
{
    return static_cast<int>(object.value(key).toDouble());
}

template<>
bool keyValue(const QJsonObject &object, const QStringView &key)
{
    return object.value(key).toBool();
}

using PublicGroupChats = QList<struct PublicGroupChat>;

struct PublicGroupChat {
    static constexpr const QStringView Address = u"address";
    static constexpr const QStringView Users = u"nusers";
    static constexpr const QStringView IsOpen = u"is_open";
    static constexpr const QStringView Name = u"name";
    static constexpr const QStringView Description = u"description";
    static constexpr const QStringView Language = u"language";

    explicit PublicGroupChat(const QJsonObject &object)
    {
        Q_ASSERT(!object.isEmpty());

        address = keyValue<QString>(object, Address);
        nusers = keyValue<int>(object, Users);
        is_open = keyValue<bool>(object, IsOpen);
        name = keyValue<QString>(object, Name);
        description = keyValue<QString>(object, Description);
        language = keyValue<QString>(object, Language);
    }

    QJsonObject toJson() const
    {
        return {
            {Address.toString(), address},
            {Users.toString(), nusers},
            {IsOpen.toString(), is_open},
            {Name.toString(), name},
            {Description.toString(), description},
            {Language.toString(), language},
        };
    }

    static PublicGroupChats fromJson(const QJsonArray &array)
    {
        PublicGroupChats groups;

        groups.reserve(array.count());

        for (const auto &value : array) {
            groups.append(PublicGroupChat{value.toObject()});
        }

        return groups;
    }

    static QJsonArray toJson(const PublicGroupChats &groups)
    {
        QJsonArray array;

        for (const auto &group : groups) {
            array.append(group.toJson());
        }

        return array;
    }

    QString address;
    int nusers;
    bool is_open;
    QString name;
    QString description;
    QString language;
};

PublicGroupChats listEntries(const QLocale &locale, QNetworkAccessManager &nm, const QString &previous)
{
    // GET /api/1.0/rooms
    // 400 - param error
    // 429 - throttled

    qInfo() << "Requesting entries with previous (" << previous << ")...";

    static qint64 total = 0;

    QUrl url(QStringLiteral("https://search.jabber.network/api/1.0/rooms"));
    QUrlQuery query;

    if (!previous.isEmpty()) {
        query.addQueryItem(QStringLiteral("after"), previous);
    }

    url.setQuery(query);

    // The REST API does not seems to support compression and always return uncompressed data
    // so the lookup is quite expensive.
    QNetworkRequest request(url);
    request.setRawHeader(QByteArrayLiteral("Accept-Encoding"), QByteArrayLiteral("gzip, deflate"));

    QEventLoop loop;
    auto reply = nm.get(request);

    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);

    loop.exec();

    reply->deleteLater();

    if (reply->error() != QNetworkReply::NoError) {
        const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

        if (statusCode == 429) {
            // Throttled for 1min
            PublicGroupChats groups;

            qInfo() << "Throttled for 1min...";

            QTimer::singleShot(1000 * 60, &nm, [&groups, &locale, &nm, &previous, &loop]() {
                groups = listEntries(locale, nm, previous);
                loop.quit();
            });

            loop.exec();
            return groups;
        }

        qWarning() << "Error:" << request.url() << reply->errorString();
        return {};
    }

    const QByteArray data = reply->readAll();
    const QJsonDocument doc = QJsonDocument::fromJson(data);

    total += data.size();

    qDebug() << "Downloaded so far:" << locale.formattedDataSize(total);

    return PublicGroupChat::fromJson(doc.object().value(QStringLiteral("items")).toArray());
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QLocale locale = QLocale::system();
    QNetworkAccessManager nm;
    PublicGroupChats all;
    PublicGroupChats groups;

    while (!(groups = listEntries(locale, nm, all.isEmpty() ? QString() : all.constLast().address)).isEmpty()) {
        all.append(groups);
        QThread::msleep(250);
    }

    const QByteArray data = QJsonDocument(PublicGroupChat::toJson(all)).toJson();

    qWarning() << "Got" << all.count() << "entries occupying" << locale.formattedDataSize(data.size()) << "uncompressed bytes,"
               << locale.formattedDataSize(qCompress(data).size()) << "compressed.";

    return 0;
}
