/*
 * 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 "MultipartDevice.h"

#include <QRandomGenerator>

#include <cstring>

namespace
{

// Escape a filename for use in a Content-Disposition header.
QString escape_filename(QString const& name)
{
    QString out = name;
    out.replace('\\', QStringLiteral("\\\\"));
    out.replace('"', QStringLiteral("\\\""));
    out.replace('\r', QString());
    out.replace('\n', QString());
    return out;
}

}  // namespace

MultipartDevice::MultipartDevice(QIODevice* payload, qint64 payload_size,
                                 QString const& filename,
                                 QString const& content_type, QObject* parent)
    : QIODevice(parent), payload_(payload), payload_size_(payload_size)
{
    QString type = content_type.isEmpty()
        ? QStringLiteral("application/octet-stream") : content_type;

    quint32 random[4];
    QRandomGenerator::system()->fillRange(random);
    QByteArray boundary = QByteArrayLiteral("----lsfproviderrclone-") +
        QByteArray(reinterpret_cast<char const*>(random),
                   int(sizeof random)).toHex();

    content_type_header_ =
        "multipart/form-data; boundary=\"" + boundary + "\"";

    prologue_ = "--" + boundary + "\r\n" +
        "Content-Disposition: form-data; name=\"file\"; filename=\"" +
        escape_filename(filename).toUtf8() + "\"\r\n" +
        "Content-Type: " + type.toUtf8() + "\r\n\r\n";
    epilogue_ = "\r\n--" + boundary + "--\r\n";

    payload_begin_ = prologue_.size();
    epilogue_begin_ = payload_begin_ + payload_size_;

    connect(payload_, &QIODevice::readyRead,
            this, &MultipartDevice::on_payload_ready_read);
    connect(payload_, &QIODevice::readChannelFinished,
            this, &MultipartDevice::on_payload_read_channel_finished);

    open(QIODevice::ReadOnly);
}

qint64 MultipartDevice::total_size() const
{
    return prologue_.size() + payload_size_ + epilogue_.size();
}

qint64 MultipartDevice::bytesAvailable() const
{
    qint64 avail = 0;
    if (pos_ < payload_begin_)
        avail += payload_begin_ - pos_;
    if (pos_ + avail < epilogue_begin_)
        avail += qMin(epilogue_begin_ - (pos_ + avail), payload_->bytesAvailable());
    if (epilogue_begin_ <= pos_ + avail && pos_ + avail < total_size())
        avail += total_size() - (pos_ + avail);
    return avail + QIODevice::bytesAvailable();
}

void MultipartDevice::on_payload_ready_read()
{
    Q_EMIT readyRead();
}

void MultipartDevice::on_payload_read_channel_finished()
{
    payload_finished_ = true;
    Q_EMIT readyRead();
}

qint64 MultipartDevice::readData(char* data, qint64 maxSize)
{
    qint64 written = 0;

    // 1. Prologue
    if (pos_ < payload_begin_)
    {
        qint64 n = qMin(maxSize, payload_begin_ - pos_);
        memcpy(data, prologue_.constData() + pos_, n);
        pos_ += n;
        written += n;
        data += n;
        maxSize -= n;
    }

    // 2. Payload (known-size sequential source)
    if (pos_ < epilogue_begin_ && maxSize != 0)
    {
        qint64 want = qMin(maxSize, epilogue_begin_ - pos_);
        qint64 n = payload_->read(data, want);
        if (n < 0) return -1;
        if (n == 0)
        {
            if (payload_finished_) return -1; // source closed early
            return written;  // wait for readyRead
        }
        pos_ += n;
        written += n;
        data += n;
        maxSize -= n;
    }

    // 3. Epilogue
    if (epilogue_begin_ <= pos_ && pos_ < total_size() && maxSize != 0)
    {
        qint64 n = qMin(maxSize, total_size() - pos_);
        memcpy(data, epilogue_.constData() + (pos_ - epilogue_begin_), n);
        pos_ += n;
        written += n;
    }

    return written;
}

qint64 MultipartDevice::writeData(char const* data, qint64 maxSize)
{
    Q_UNUSED(data);
    Q_UNUSED(maxSize);
    return -1;  // read-only
}
