/*
 * Copyright (c) KylinSoft  Co., Ltd. 2024. All rights reserved.
 *
 * kaiming is licensed under the GPL v2.0+.
 * 
 * See the LICENSE file for more details.
 */

#include "KMOSTreeHandler.h"

#include <sys/stat.h>  // For chmod
#include <iostream>
#include <queue>
#include <glib.h>

#include "common/KMLogger.h"

typedef struct
{
    const char *commit;
    guint64 timestamp;
    guint commitCacheVersion;
    bool indexedDeltas;

    const char *title;
    const char *comment;
    const char *description;
    const char *homepage;
    const char *icon;
    const char *collection_id;
    const char *commitDefaultBranch;
    const char *commitRedirectUrl;
    const char *deployCollectionId;
    const char *commitAuthenticatorName;

    GFile *root;
    GVariant *metadata;
} CommitInfo;

class KMOSTreeHandler::Private
{
public:
    fs::path m_repoPath;
    OstreeRepo *m_ostreeRepo;
    map<string, CommitInfo> m_mapCommitInfo;

    bool m_initialized = false;

    OstreeAsyncProgress *m_pullProgress;
    CallBack m_progressCallback;
    GError *m_error = nullptr;
};

static ProgressStatus progressStatus;
static std::deque<std::string> globalQueuePull;

static void progress_change(OstreeAsyncProgress *progress, gpointer userData)
{
    KMOSTreeHandler *manager = (KMOSTreeHandler *)userData;

    bool downloading_extra_data, caught_error;

    ostree_async_progress_get(progress,
                              "transferred-extra-data-bytes", "t", &progressStatus.transferredExtraDataBytes,
                              "total-extra-data-bytes", "t", &progressStatus.totalExtraDataBytes,
                              "status", "s", &progressStatus.ostreeStatus,
                              "start-time", "t", &progressStatus.startTime,
                              "bytes-transferred", "t", &progressStatus.bytesTransferred,
                              "fetched-delta-part-size", "t", &progressStatus.fetchedDeltaPartSize,
                              "total-delta-part-size", "t", &progressStatus.totalDeltaPartSize,
                              "fetched", "u", &progressStatus.fetched,
                              "requested", "u", &progressStatus.requested,
                              "outstanding-metadata-fetches", "u", &progressStatus.outstandingMetadataFetches,
                              "metadata-fetched", "u", &progressStatus.metadataFetched,
                              "total-delta-parts", "u", &progressStatus.totalDeltaParts,
                              //   "downloading-extra-data", "u", downloading_extra_data, // 存在段错误
                              nullptr);

    progressStatus.ref = globalQueuePull.front();
    kmlogger.info("progressStatus ==>  status : %s  ref: %s queuePullSize: %d requested: %ld fetched: %ld ", progressStatus.ostreeStatus != NULL ? progressStatus.ostreeStatus : "", progressStatus.ref.c_str(), globalQueuePull.size(), progressStatus.requested, progressStatus.fetched);
    manager->updateProgress();

    if (globalQueuePull.size() > 1 && ((progressStatus.requested > 0 && progressStatus.fetched == progressStatus.requested) || *progressStatus.ostreeStatus))
    {
        kmlogger.info("开始 pop front for ref %s , ", progressStatus.ref.c_str());
        globalQueuePull.pop_front();
    }
}

KMOSTreeHandler::KMOSTreeHandler(const string &path)
    : d(std::make_unique<Private>())
{
    g_autoptr(GFile) pathFile = g_file_new_for_path(path.data());
    d->m_ostreeRepo = ostree_repo_new(pathFile);
    d->m_repoPath = path;

    if(!ostree_repo_open(d->m_ostreeRepo, nullptr, nullptr))
    {
        KMFolder foder;
        foder.ensureRepoForSudo(false);
        d->m_ostreeRepo = ostree_repo_new(pathFile);
        ostree_repo_open(d->m_ostreeRepo, nullptr, nullptr);
    }

    d->m_initialized = true;
    kmlogger.info("init ostree handler successful!");
}

KMOSTreeHandler::~KMOSTreeHandler()
{
    if(d->m_ostreeRepo)
    {
        g_object_unref(d->m_ostreeRepo);    //释放资源，fix：软件商店bug324031,323973
    }
}

string KMOSTreeHandler::readLatestCommit(const string &refCompletedId)
{
    char *res = NULL;
    // string refSpec = d->m_repoPath.append(":").append(refCompletedId);
    string refSpec = refCompletedId;
    replaceRef(refSpec);

    if (!ostree_repo_resolve_rev(d->m_ostreeRepo, refSpec.c_str(), false, &res, &d->m_error))
    {
        kmlogger.error("Failed to get latest commit for branch %s", refSpec.c_str() );
        return "";
    }

    string outRes = string(res);
    return outRes;
}

std::string KMOSTreeHandler::getRemoteUrl(const string &remoteName)
{
    if (!d->m_ostreeRepo || remoteName.empty())
    {
        return "-";
    }

    char *tmp_url = nullptr;
    if (!ostree_repo_remote_get_url(d->m_ostreeRepo, remoteName.c_str(), &tmp_url, &d->m_error))
    {
        return "-";
    }

    std::string cached_url(tmp_url);
    g_free(tmp_url);

    std::string configPath = "/opt/kaiming/repo/config";
    std::string local_url = getLocalRemoteUrl(configPath, remoteName);

    if (local_url != cached_url && local_url != "-")
    {
        return local_url;
    }

    return cached_url;
}

std::string KMOSTreeHandler::getLocalRemoteUrl(const std::string &configPath, const std::string &remoteName)
{
    GKeyFile *keyfile = g_key_file_new();
    if (!g_key_file_load_from_file(keyfile, configPath.c_str(), G_KEY_FILE_NONE, nullptr))
    {
        g_key_file_free(keyfile);
        return "-";
    }

    std::string group = "remote \"" + remoteName + "\"";
    gchar *url = g_key_file_get_string(keyfile, group.c_str(), "url", nullptr);
    g_key_file_free(keyfile);

    if (!url)
    {
        return "-";
    }

    std::string result(url);
    g_free(url);
    return result;
}

void KMOSTreeHandler::replaceRef(std::string& ref) {
    size_t pos = 0;
    while ((pos = ref.find('+', pos)) != std::string::npos) {
        ref.replace(pos, 1, "--");
        pos += 2;
    }
}

bool KMOSTreeHandler::pull(const string &remoteName, vector<string> vecRefs, CallBack cb)
{
    initProgress();
    d->m_progressCallback = cb;
    progressStatus.lastRef = vecRefs.back();

    // globalQueuePull.clear();

    for (auto ref : vecRefs)
    {
        replaceRef(ref);

        globalQueuePull.push_back(ref);
        char *refs[2] = { (char *)ref.c_str(), nullptr };
        g_clear_error(&d->m_error);
        bool ostreePullRes = ostree_repo_pull(d->m_ostreeRepo, remoteName.c_str(), refs, OSTREE_REPO_PULL_FLAGS_NONE, d->m_pullProgress, nullptr, &d->m_error);
        if (!ostreePullRes)
        {
            if (d->m_error != nullptr)
            {
                std::cerr << "ostree pull failed: " << d->m_error->message << std::endl;
                kmlogger.error("ostree pull failed: %s", d->m_error->message);
                g_error_free(d->m_error);
                d->m_error = nullptr;
            }
            else
            {
                std::cerr << "ostree pull failed with unknown error" << std::endl;
                kmlogger.error( "ostree pull failed with unknown error");
            }

            return false; 
        }
    }
    ostree_async_progress_finish(d->m_pullProgress);
    globalQueuePull.clear();
    return true;
}

bool KMOSTreeHandler::deleteRefTag(const string &remoteName, const string &ref, const string &commit)
{
    GError *err = nullptr;
    string strRef = ref;

    replaceRef(strRef);

    if (!ostree_repo_set_ref_immediate(d->m_ostreeRepo, remoteName != "" ? remoteName.c_str() : nullptr, strRef.c_str(), NULL, NULL, &err))
    {
        kmlogger.error("delete repo ref %s error (%s)", ref.c_str(), err->message);
        return false;
    }
    kmlogger.info("delete ref %s in remote %s", ref.c_str(), remoteName.c_str());
    return true;
}

bool KMOSTreeHandler::repoPrune()
{
    GError *err = nullptr;
    gint objectsTotal, objectsPruned;
    guint64 prunedObjectSizeTotal;
    if (!ostree_repo_prune(d->m_ostreeRepo, OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, 0, &objectsTotal, &objectsPruned, &prunedObjectSizeTotal, NULL, &err))
    {
        kmlogger.error("prune repo error (%s)", err->message);
        return false;
    }
    return true;
}

void KMOSTreeHandler::updateProgress()
{
    if (d->m_progressCallback.operator bool())
    {
        d->m_progressCallback(progressStatus);
    }
}

bool KMOSTreeHandler::checkout(const string &commit, const string &tmpTargetPath, const string &subPath)
{
    OstreeRepoCheckoutAtOptions options = {};
    options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
    options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
    options.enable_fsync = FALSE;
    options.bareuseronly_dirs = TRUE;
    if (subPath.length() > 0)
    {
        options.subpath = subPath.c_str();
    }
    g_clear_error(&d->m_error);

    // 获取指定的 commit 对象
    // g_autoptr(GVariant) commitVariant = nullptr;
    // commitVariant = getCommitData(commit)

    bool checkoutRes = ostree_repo_checkout_at(d->m_ostreeRepo, &options, AT_FDCWD, tmpTargetPath.c_str(), commit.c_str(), nullptr, &d->m_error);
    if (!checkoutRes && d->m_error)
    {
        kmlogger.error(d->m_error->message);
        return false;
    }
    return true;
}

/**
 * @brief 创建 deploy 时的ostree refs
 *
 * ostree refs 查看： `ostree refs --repo repo`
 *      deploy/app/org.kde.kclock/x86_64/master
 *      kaiming-repo:app/org.kde.kclock/x86_64/master
 *
 * @param completedRef 完整的ref，如 app/org.kde.kclock/x86_64/master
 * @param commit ref对应的commit, 如 f3b1314b51f2b8865089f13cbae5b7cd449b169f0b36530d27da74beb68aa492
 * @return true
 * @return false
 */
bool KMOSTreeHandler::updateDeployRef(const string &completedRef, const string &commit)
{
    string deployRef = string("deploy/").append(completedRef);
    replaceRef(deployRef);

    if (!ostree_repo_set_ref_immediate(d->m_ostreeRepo, nullptr, deployRef.c_str(), commit.c_str(), nullptr, nullptr))
    {
        return false;
    }

    return true;
}

void KMOSTreeHandler::initProgress()
{
    d->m_pullProgress = ostree_async_progress_new();

    ostree_async_progress_set(d->m_pullProgress,
                              "transferred-extra-data-bytes", "t", 0,
                              "total-extra-data-bytes", "t", 0,
                              "status", "s", "",
                              "start-time", "t", 0,
                              "bytes-transferred", "t", 0,
                              "fetched-delta-part-size", "t", 0,
                              "total-delta-part-size", "t", 0,
                              "fetched", "u", 0,
                              "requested", "u", 0,
                              "outstanding-metadata-fetches", "u", 0,
                              "metadata-fetched", "u", 0,
                              "total-delta-parts", "u", 0,
                              nullptr);

    g_signal_connect(d->m_pullProgress, "changed", G_CALLBACK(progress_change), (gpointer)this);
}

map<string, KMRef> KMOSTreeHandler::getLocalRefs(const string &remoteName)
{
    map<string, KMRef> mapRef;

    g_autoptr(GHashTable) table = NULL;
    g_clear_error(&d->m_error);
    if (!ostree_repo_list_refs(d->m_ostreeRepo, nullptr, &table, nullptr, &d->m_error))
    {
        return mapRef;
    }

    if (!table)
    {
        return mapRef;
    }

    gpointer key, value;
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, table);
    while (g_hash_table_iter_next(&iter, &key, &value))
    {
        KMRef ref;
        char *refspec = (char *)key;
        char *checksum = (char *)value;

        list<string> listStr = KMUtil::split(refspec, ':');
        if (listStr.size() != 2)
        {
            continue;
        }

        list<string> refDescrible = KMUtil::split(listStr.back(), '/');

        if (refDescrible.size() != 6)//  原为4
        {
            continue;
        }
        ref.branch = refDescrible.front();
        refDescrible.pop_front();

        ref.arch = refDescrible.front();
        refDescrible.pop_front();
       
        ref.kind = refDescrible.front();
        refDescrible.pop_front();

        ref.id = refDescrible.front();
        refDescrible.pop_front();

        ref.module = refDescrible.front();
        refDescrible.pop_front();
 
        ref.version = refDescrible.front();
        refDescrible.pop_front();

        mapRef[checksum] = ref;
        g_hash_table_iter_steal(&iter);
    }
    return mapRef;
}

// std::string KMOSTreeHandler::getBranchTimestamp(std::string& targetBranch) {
//     g_autoptr(GError) error = nullptr;

//     replaceRef(targetBranch); // '+' -> '--'

//     char *refs[2] = { const_cast<char*>(targetBranch.c_str()), nullptr };
//     if (!ostree_repo_pull(d->m_ostreeRepo, REPO_NAME, refs, OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY, nullptr, nullptr, &error))
//     {
//         // g_printerr("Pull failed: %s\n", error->message);
//         kmlogger.error("Pull failed: %s\n", error->message);
//         return "";
//     }

//     g_autofree gchar *commit_id = nullptr;
//     if (!ostree_repo_resolve_rev(d->m_ostreeRepo, targetBranch.c_str(), FALSE, &commit_id, &error)) {
//         kmlogger.error("Failed to resolve branch %s: %s\n", targetBranch.c_str(), error->message);
//         return "";
//     }

//     g_autoptr(GVariant) commit_variant = nullptr;
//     if (!ostree_repo_load_commit(d->m_ostreeRepo, commit_id, &commit_variant, nullptr, &error)) {
//         kmlogger.error("Failed to load commit %s: %s\n", commit_id, error->message);
//         return "";
//     }

//     gint64 timestamp = ostree_commit_get_timestamp(commit_variant);

//     char buf[64] = { 0 };
//     time_t t = timestamp;
//     struct tm tm;
//     gmtime_r(&t, &tm);
//     strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
//     return std::string(buf);
// }

std::string KMOSTreeHandler::getBranchTimestamp(const char *target_branch) {
    g_autoptr(GError) error = nullptr;
    g_autoptr(GBytes) summaryBytes = nullptr;
    g_autoptr(GBytes) sigBytes = nullptr;

    if (!ostree_repo_remote_fetch_summary(d->m_ostreeRepo, REPO_NAME, &summaryBytes, &sigBytes, NULL, &error)) {
        g_printerr("Failed to fetch summary: %s\n", error->message);
        return "";
    }

    g_autofree char *summaryPath = g_strdup("/opt/kaiming/repo/summary");
    FILE *fp = fopen(summaryPath, "wb");
    if (!fp) {
        g_printerr("Failed to create summary file\n");
        return "";
    }
    fwrite(g_bytes_get_data(summaryBytes, NULL), 1, g_bytes_get_size(summaryBytes), fp);
    fclose(fp);

    std::string cmd = "ostree summary --view --repo=" + std::string("/opt/kaiming/repo") + " | grep -A 5 '* " + target_branch + "' | grep 'Timestamp'";

    std::array<char, 128> buffer;
    std::string rawTimestamp;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe)
    {
        return "";
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()))
    {
        rawTimestamp += buffer.data();
    }

    std::string timestamp;
    const char* p = rawTimestamp.c_str();

    while (*p && isspace(*p)) p++;

    if (strncmp(p, "Timestamp", 9) == 0) {
        p += 9;
        while (*p && *p != ':') p++;
        if (*p == ':') {
            p++;
            while (*p && isspace(*p)) p++;
            if (strlen(p) >= 20) {
                char clean[20];
                memcpy(clean, p, 10);
                clean[10] = ' ';
                memcpy(clean+11, p+11, 8);
                timestamp.assign(clean, 19);
            }
        }
    }

    return timestamp;
}
