/*
 * Copyright (C) 2026 UBports Foundation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "RcloneClient.h"
#include "rclone_error.h"

#include <QJsonDocument>
#include <QNetworkReply>

#include <lomiri/storage/provider/Exceptions.h>

using namespace std;
using namespace lomiri::storage::provider;

RcloneClient::RcloneClient(QObject* parent)
    : QObject(parent)
{
    QByteArray credentials = daemon_.user().toUtf8() + ":" +
                             daemon_.password().toUtf8();
    auth_header_ = QByteArrayLiteral("Basic ") + credentials.toBase64();

    connect(&daemon_, &RcloneDaemon::listening,
            this, &RcloneClient::on_daemon_listening);
    connect(&daemon_, &RcloneDaemon::ready,
            this, &RcloneClient::on_daemon_ready);
    connect(&daemon_, &RcloneDaemon::failed,
            this, &RcloneClient::on_daemon_failed);
}

RcloneClient::~RcloneClient() = default;

QJsonObject RcloneClient::make_params(string const& fs, string const& remote)
{
    return QJsonObject{
        {QStringLiteral("fs"), QString::fromStdString(fs)},
        {QStringLiteral("remote"), QString::fromStdString(remote)},
    };
}

QJsonObject RcloneClient::make_sync_params(string const& fs,
                                           string const& src_remote,
                                           string const& dst_remote,
                                           bool concat_remote)
{
    if (concat_remote)
    {
        return QJsonObject{
            {QStringLiteral("srcFs"), QString::fromStdString(fs + src_remote)},
            {QStringLiteral("dstFs"), QString::fromStdString(fs + dst_remote)},
        };
    }
    return QJsonObject{
        {QStringLiteral("srcFs"), QString::fromStdString(fs)},
        {QStringLiteral("srcRemote"), QString::fromStdString(src_remote)},
        {QStringLiteral("dstFs"), QString::fromStdString(fs)},
        {QStringLiteral("dstRemote"), QString::fromStdString(dst_remote)},
    };
}

QNetworkRequest RcloneClient::authenticated(QUrl const& url) const
{
    QNetworkRequest request(url);
    request.setRawHeader(QByteArrayLiteral("Authorization"), auth_header_);
    return request;
}

// Build rclone URL: http://host:port/[remote:path]/path/to/object
// See: https://rclone.org/rc/#rc-serve
QUrl RcloneClient::serve_url(string const& fs, string const& remote_path) const
{
    QByteArray encoded = daemon_.base_url().toEncoded();  // http://host:port/
    encoded += '[';
    encoded += QUrl::toPercentEncoding(QString::fromStdString(fs), "/:");
    encoded += ']';
    for (auto const& segment :
         QString::fromStdString(remote_path).split('/', Qt::SkipEmptyParts))
    {
        encoded += '/';
        encoded += QUrl::toPercentEncoding(segment);
    }
    return QUrl::fromEncoded(encoded);
}

void RcloneClient::call(QString const& endpoint, QJsonObject const& params,
                        string const& item_id, RcCallback cb)
{
    PendingCall pc{endpoint, params, item_id, std::move(cb)};
    switch (daemon_.state())
    {
    case RcloneDaemon::State::Ready:
        dispatch(pc);
        break;
    case RcloneDaemon::State::Failed:
        pc.cb(QJsonObject(), daemon_failure());
        break;
    case RcloneDaemon::State::Starting:
        pending_calls_.push_back(std::move(pc));
        break;
    }
}

void RcloneClient::call(QString const& endpoint, QJsonObject const& params,
                        RcCallback cb)
{
    call(endpoint, params, string(), std::move(cb));
}

void RcloneClient::when_ready(RcCallback cb)
{
    call(QString(), QJsonObject(), string(), std::move(cb));
}

void RcloneClient::dispatch(PendingCall const& pc)
{
    if (pc.endpoint.isEmpty())
    {
        pc.cb(QJsonObject(), boost::exception_ptr());
        return;
    }

    QUrl url = daemon_.base_url().resolved(QUrl(pc.endpoint));
    QNetworkRequest request = authenticated(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader,
                      QByteArrayLiteral("application/json"));
    QByteArray body = QJsonDocument(pc.params).toJson(QJsonDocument::Compact);

    QNetworkReply* reply = network_.post(request, body);
    string item_id = pc.item_id;
    RcCallback cb = pc.cb;

    connect(reply, &QNetworkReply::finished, this, [reply, item_id, cb] {
        reply->deleteLater();
        QByteArray data = reply->readAll();
        int status = reply->attribute(
            QNetworkRequest::HttpStatusCodeAttribute).toInt();

        if (status == 200)
        {
            QJsonParseError perr;
            QJsonDocument doc = QJsonDocument::fromJson(data, &perr);

            if (perr.error != QJsonParseError::NoError || !doc.isObject())
            {
                cb(QJsonObject(), boost::copy_exception(RemoteCommsException(
                    "Malformed rclone rc response: " +
                    perr.errorString().toStdString())));
                return;
            }

            cb(doc.object(), boost::exception_ptr());
        }
        else
        {
            cb(QJsonObject(), translate_rclone_error(reply, data, item_id));
        }
    });
}

boost::exception_ptr RcloneClient::daemon_failure() const
{
    return boost::copy_exception(ResourceException(
        "rclone daemon unavailable: " + daemon_.error_string().toStdString(), 0));
}

// Confirm the API is up, declare the daemon ready.
void RcloneClient::on_daemon_listening()
{
    dispatch({QStringLiteral("rc/noop"), QJsonObject(), string(),
              [this](QJsonObject const&, boost::exception_ptr const& error) {
                  if (error) daemon_.set_failed(error);
                  else daemon_.set_ready();
              }});
}

void RcloneClient::on_daemon_ready()
{
    auto calls = std::move(pending_calls_);
    pending_calls_.clear();
    for (auto const& pc : calls) dispatch(pc);
}

void RcloneClient::on_daemon_failed(QString const& reason)
{
    Q_UNUSED(reason);
    boost::exception_ptr err = daemon_failure();
    auto calls = std::move(pending_calls_);
    pending_calls_.clear();
    for (auto const& pc : calls) pc.cb(QJsonObject(), err);
}
