/****************************************************************************
 *   Copyright (C) 2018-2026 Savoir-faire Linux Inc.                        *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 of the License, or (at your option) any later version.     *
 *                                                                          *
 *   This library 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      *
 *   Lesser 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/>.  *
 ***************************************************************************/

// LRC
#include "api/datatransfermodel.h"

// Dbus
#include "dbus/configurationmanager.h"

// libjami
#include <datatransfer_interface.h>

// Std
#include <map>
#include <stdexcept>
#include <type_traits>

// Qt
#include <QDir>
#include <QFileInfo>
#include <QtCore/QStandardPaths>
#include <QUuid>

namespace lrc {
namespace api {

// LIBJAMI to LRC event code conversion
static inline datatransfer::Status
convertDataTransferEvent(libjami::DataTransferEventCode event)
{
    switch (event) {
    case libjami::DataTransferEventCode::invalid:
        return datatransfer::Status::INVALID;
    case libjami::DataTransferEventCode::created:
        return datatransfer::Status::on_connection;
    case libjami::DataTransferEventCode::unsupported:
        return datatransfer::Status::unsupported;
    case libjami::DataTransferEventCode::wait_peer_acceptance:
        return datatransfer::Status::on_connection;
    case libjami::DataTransferEventCode::wait_host_acceptance:
        return datatransfer::Status::on_connection;
    case libjami::DataTransferEventCode::ongoing:
        return datatransfer::Status::on_progress;
    case libjami::DataTransferEventCode::finished:
        return datatransfer::Status::success;
    case libjami::DataTransferEventCode::closed_by_host:
        return datatransfer::Status::stop_by_host;
    case libjami::DataTransferEventCode::closed_by_peer:
        return datatransfer::Status::stop_by_peer;
    case libjami::DataTransferEventCode::invalid_pathname:
        return datatransfer::Status::invalid_pathname;
    case libjami::DataTransferEventCode::unjoinable_peer:
        return datatransfer::Status::unjoinable_peer;
    case libjami::DataTransferEventCode::timeout_expired:
        return datatransfer::Status::timeout_expired;
    }
    throw std::runtime_error("BUG: broken convertDataTransferEvent() switch");
}

class DataTransferModel::Impl : public QObject
{
    Q_OBJECT

public:
    Impl(DataTransferModel& up_link);

    QString getUniqueFilePath(const QString& filename, const QString& path = "");

    DataTransferModel& upLink;
    MapStringString file2InteractionId;
    MapStringString interactionToFileId; // stricly the reverse map of file2InteractionId
};

DataTransferModel::Impl::Impl(DataTransferModel& up_link)
    : QObject {}
    , upLink {up_link}
{}

QString
DataTransferModel::Impl::getUniqueFilePath(const QString& filename, const QString& path)
{
    auto base = filename;
    QString ext = QFileInfo(base).completeSuffix();
    if (!ext.isEmpty())
        ext = ext.prepend(".");

    QFileInfo fi(filename);
    auto p = !path.isEmpty() ? path : fi.dir().path();
    base = QDir(p).filePath(fi.baseName() + ext);
    if (!QFile::exists(base))
        return base;

    base.chop(ext.size());
    QString ret;
    for (int suffix = 1;; suffix++) {
        ret = QString("%1 (%2)%3").arg(base).arg(suffix).arg(ext);
        if (!QFile::exists(ret)) {
            return ret;
        }
    }
}

void
DataTransferModel::registerTransferId(const QString& fileId, const QString& interactionId)
{
    pimpl_->file2InteractionId[fileId] = interactionId;
    pimpl_->interactionToFileId.remove(interactionId); // Because a file transfer can be retried
    pimpl_->interactionToFileId[interactionId] = fileId;
}

DataTransferModel::DataTransferModel()
    : QObject(nullptr)
    , pimpl_ {std::make_unique<Impl>(*this)}
{}

DataTransferModel::~DataTransferModel() = default;

void
DataTransferModel::sendFile(const QString& accountId,
                            const QString& conversationId,
                            const QString& filePath,
                            const QString& displayName,
                            const QString& parent)
{
    ConfigurationManager::instance().sendFile(accountId, conversationId, filePath, displayName, parent);
}

void
DataTransferModel::fileTransferInfo(const QString& accountId,
                                    const QString& conversationId,
                                    const QString& fileId,
                                    QString& path,
                                    qlonglong& total,
                                    qlonglong& progress)
{
    ConfigurationManager::instance().fileTransferInfo(accountId, conversationId, fileId, path, total, progress);
}

void
DataTransferModel::download(const QString& accountId,
                            const QString& convId,
                            const QString& interactionId,
                            const QString& fileId,
                            const QString& path)
{
    ConfigurationManager::instance().downloadFile(accountId, convId, interactionId, fileId, path);
}

QString
DataTransferModel::copyTo(const QString& accountId,
                          const QString& convId,
                          const QString& interactionId,
                          const QString& destPath,
                          const QString& displayName)
{
    auto fileId = getFileIdFromInteractionId(interactionId);
    if (fileId.isEmpty()) {
        qWarning() << "Cannot find any file for " << interactionId;
        return {};
    }
    QString path;
    qlonglong total, progress;

    fileTransferInfo(accountId, convId, fileId, path, total, progress);

    auto src = QFile(path);
    auto srcfi = QFileInfo(path);
    if (!src.exists())
        return {};

    auto filename = displayName;
    if (displayName.isEmpty())
        filename = srcfi.isSymLink() ? srcfi.symLinkTarget() : path;
    auto dest = pimpl_->getUniqueFilePath(filename, destPath);
    qDebug() << "Copy to " << dest;
    // create directory if it does not exist
    QDir dir(destPath);
    if (!dir.exists())
        dir.mkpath(".");
    src.copy(dest);
    return dest;
}

void
DataTransferModel::cancel(const QString& accountId, const QString& conversationId, const QString& interactionId)
{
    ConfigurationManager::instance().cancelDataTransfer(accountId,
                                                        conversationId,
                                                        getFileIdFromInteractionId(interactionId));
}

QString
DataTransferModel::getInteractionIdFromFileId(const QString& fileId)
{
    return pimpl_->file2InteractionId[fileId];
}

QString
DataTransferModel::getFileIdFromInteractionId(const QString& interactionId)
{
    return pimpl_->interactionToFileId[interactionId];
}

QString
DataTransferModel::createDefaultDirectory()
{
    auto defaultDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/Jami";
    QDir dir(defaultDirectory);
    if (!dir.exists())
        dir.mkpath(".");
    return defaultDirectory;
}

} // namespace api
} // namespace lrc

#include "api/moc_datatransfermodel.cpp"
#include "datatransfermodel.moc"
