/*
 * Copyright (c) 2025 Meltytech, LLC
 *
 * 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 "FlatpakWrapperGenerator.h"
#include "Logger.h"

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QRegularExpression>
#include <QTextStream>

static QString trim(const QString &s)
{
    QString t = s;
    return t.trimmed();
}

QList<AppEntry> FlatpakWrapperGenerator::listInstalledApps() const
{
    QProcess p;

#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
    auto env = QProcessEnvironment::systemEnvironment();
    env.remove("LD_LIBRARY_PATH");
    LOG_DEBUG() << env.toStringList();
    p.setProcessEnvironment(env);
#endif

    p.start("/usr/bin/flatpak", {"list", "--app", "--columns=application,branch"});
    if (!p.waitForStarted(5000)) {
        LOG_WARNING() << "Failed to start flatpak";
        return {};
    }
    p.waitForFinished(10000);
    const QString out = QString::fromUtf8(p.readAllStandardOutput());
    QList<AppEntry> apps;
    for (const QString &line : out.split('\n', Qt::SkipEmptyParts)) {
        const QString s = trim(line);
        if (s.isEmpty())
            continue;
        // Expect lines like: "org.gimp.GIMP\tstable" or space-separated
        QStringList parts = s.split(QRegularExpression("\t+| +"), Qt::SkipEmptyParts);
        if (parts.size() >= 2) {
            const QString appId = parts.at(0);
            const QString branch = parts.at(1);
            // Skip potential header line if present
            if (!appId.contains('.'))
                continue;
            apps.push_back(AppEntry{appId, branch});
        } else if (parts.size() == 1) {
            const QString appId = parts.at(0);
            if (!appId.contains('.'))
                continue;
            apps.push_back(AppEntry{appId, QStringLiteral("stable")});
        }
    }
    return apps;
}

bool FlatpakWrapperGenerator::ensureOutputDir() const
{
    if (m_outputDir.isEmpty())
        return false;
    QDir dir(m_outputDir);
    if (dir.exists())
        return true;
    if (m_dryRun)
        return true;
    return dir.mkpath(".");
}

bool FlatpakWrapperGenerator::generateAllInstalled()
{
    const QList<AppEntry> apps = listInstalledApps();
    if (apps.isEmpty()) {
        LOG_WARNING() << "No Flatpak apps found or flatpak not available.";
        return false;
    }
    if (!ensureOutputDir()) {
        LOG_WARNING() << "Failed to ensure output directory" << m_outputDir;
        return false;
    }
    bool allOk = true;
    for (const AppEntry &app : apps) {
        if (!writeWrapper(app))
            allOk = false;
    }
    return allOk;
}

bool FlatpakWrapperGenerator::generateFor(const QStringList &appIds)
{
    if (!ensureOutputDir()) {
        LOG_WARNING() << "Failed to ensure output directory" << m_outputDir;
        return false;
    }
    bool allOk = true;
    for (const QString &id : appIds) {
        const QString branch = getBranchForAppId(id);
        if (!writeWrapper(AppEntry{id, branch}))
            allOk = false;
    }
    return allOk;
}

QString FlatpakWrapperGenerator::getBranchForAppId(const QString &appId) const
{
    // Query branch for specific app-id
    QProcess p;
    p.start("flatpak", {"info", appId, "--show-branch"});
    if (!p.waitForStarted(5000)) {
        LOG_WARNING() << "Failed to start flatpak for branch query";
        return QStringLiteral("stable");
    }
    p.waitForFinished(10000);
    QString out = QString::fromUtf8(p.readAllStandardOutput()).trimmed();
    if (out.isEmpty())
        out = QStringLiteral("stable");
    return out;
}

QString FlatpakWrapperGenerator::buildWrapperScript(const AppEntry &app) const
{
    QString script;
    script += "#!/usr/bin/env sh\n";
    script += "# Auto-generated by Shotcut\n";
    script += "exec /usr/bin/flatpak run --branch=" + app.branch + " " + app.appId + " \"$@\"\n";
    return script;
}

bool FlatpakWrapperGenerator::writeWrapper(const AppEntry &app) const
{
    // Use the 3rd dotted segment (index 2) as the name...
    QStringList segs = app.appId.split('.');
    QString baseName = segs.size() >= 3 ? segs.at(2) : app.appId.section('.', -1);
    if (app.appId == QStringLiteral("fr.handbrake.ghb")) {
        baseName = QStringLiteral("Handbrake");
    }
    // ...followed by the branch if not stable
    if (!app.branch.isEmpty() && app.branch != QLatin1String("stable")) {
        baseName += '-' + app.branch;
    }
    baseName = m_prefix + baseName;
    const QString path = QDir(m_outputDir).filePath(baseName);

    const QString script = buildWrapperScript(app);

    if (QFileInfo::exists(path) && !m_force) {
        LOG_INFO() << "Skip (exists):" << path;
        return true;
    }

    LOG_INFO() << (m_dryRun ? "Would write:" : "Writing:") << path;

    if (m_dryRun)
        return true;

    QFile f(path);
    if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
        LOG_WARNING() << "Failed to open for write:" << path;
        return false;
    }
    QTextStream ts(&f);
    ts << script;
    f.close();

    // Make it executable
    QFile::Permissions perms = f.permissions();
    perms |= QFile::ExeOwner | QFile::ExeGroup | QFile::ExeOther;
    f.setPermissions(perms);
    return true;
}
