#include "KMOSTreeHandler.h"

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

#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))
    {
        d->m_initialized = true;
    }

    kmlogger.info("init ostree handler successful!");
}

KMOSTreeHandler::~KMOSTreeHandler() { }

string KMOSTreeHandler::readLatestCommit(const string &refCompletedId)
{
    char *res = NULL;
    // string refSpec = d->m_repoPath.append(":").append(refCompletedId);
    string refSpec = refCompletedId;
    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;
}

GVariant *KMOSTreeHandler::getCommitData(const string &commit)
{
    GVariant *commitDataGVariant = NULL;

    if (!d->m_ostreeRepo)
    {
        return NULL;
    }

    CommitInfo info;
    info.commit = commit.data();

    if (!ostree_repo_load_commit(d->m_ostreeRepo, info.commit, &commitDataGVariant, nullptr, nullptr))
    {
        kmlogger.error("get commit data error.");
        return NULL;
    }

    return commitDataGVariant;
}

bool KMOSTreeHandler::loadCommit(const string &commit)
{
    GVariant *commitDataGVariant;

    if (!d->m_ostreeRepo)
    {
        return false;
    }

    CommitInfo info;
    info.commit = commit.data();

    if (!ostree_repo_load_commit(d->m_ostreeRepo, info.commit, &commitDataGVariant, nullptr, nullptr))
    {
        kmlogger.error("load commit error.");
        return false;
    }

    if (!ostree_repo_read_commit(d->m_ostreeRepo, info.commit, &info.root, nullptr, nullptr, &d->m_error))
    {
        kmlogger.error("read commit error.");
        return false;
    }

    if (!ostree_repo_read_commit_detached_metadata(d->m_ostreeRepo, info.commit, &info.metadata, nullptr, &d->m_error))
    {
        kmlogger.error("read commit detached metadata error.");
        return false;
    }

    if (g_variant_lookup(commitDataGVariant, "xa.cache-version", "u", &info.commitCacheVersion))
    {
        kmlogger.debug("cache version : %d\n", info.commitCacheVersion);
    }

    if (g_variant_lookup(commitDataGVariant, "ostree.summary.indexed-deltas", "b", &info.indexedDeltas))
    {
        kmlogger.debug("index deltas : %s\n", info.indexedDeltas ? "true" : "false");
    }

    if (g_variant_lookup(commitDataGVariant, "xa.title", "&s", &info.title))
    {
        kmlogger.debug("title : %s\n", info.title);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.comment", "&s", &info.comment))
    {
        kmlogger.debug("comment : %s\n", info.comment);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.description", "&s", &info.description))
    {
        kmlogger.info("description : %s\n", info.description);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.homepage", "&s", &info.homepage))
    {
        kmlogger.debug("homepage : %s\n", info.homepage);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.icon", "&s", &info.icon))
    {
        kmlogger.debug("icon : %s\n", info.icon);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.collection-id", "&s", &info.collection_id))
    {
        kmlogger.debug("collection id : %s\n", info.collection_id);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.default-branch", "&s", &info.commitDefaultBranch))
    {
        kmlogger.debug("default branch : %s\n", info.commitDefaultBranch);
    }

    if (g_variant_lookup(commitDataGVariant, "xa.authenticator-name", "&s", &info.commitAuthenticatorName))
    {
        kmlogger.debug("authenticator name : %s\n", info.commitAuthenticatorName);
    }

    if (g_variant_lookup(commitDataGVariant, OSTREE_META_KEY_DEPLOY_COLLECTION_ID, "&s", &info.deployCollectionId))
    {
        kmlogger.debug("deploy collection id : %s\n", info.deployCollectionId);
    }

    info.timestamp = ostree_commit_get_timestamp(commitDataGVariant);

    d->m_mapCommitInfo[info.commit] = info;

    return true;
}

fs::path KMOSTreeHandler::getRepoPath()
{
    return d->m_repoPath;
}

OstreeRepo *KMOSTreeHandler::getRepo()
{
    return d->m_ostreeRepo;
}

fs::path KMOSTreeHandler::getRootPath(const string &commit)
{
    if (!d->m_ostreeRepo || !checkCommit(commit))
    {
        return fs::path();
    }

    return fs::path(g_file_get_path(d->m_mapCommitInfo[commit].root));
}

uint64_t KMOSTreeHandler::getCommitTimestamp(const string &commit)
{
    if (!checkCommit(commit))
    {
        return 0;
    }
    return d->m_mapCommitInfo[commit].timestamp;
}

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

    unique_ptr<char> remote_url = nullptr;
    char *tmp_url = remote_url.get();
    g_clear_error(&d->m_error);
    if (!ostree_repo_remote_get_url(d->m_ostreeRepo, remoteName.data(), &tmp_url, &d->m_error))
    {
        return "-";
    }
    return string(tmp_url);
}

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;
}

/**
 * @brief 获取 remote 对应已安装并部署的所有 app 和 runtime
 *
 * 此接口比较准确判断当前软件包是否已成功安装 : 一切以 deploy/** 为准
 *
 * @param remoteName
 * @return map<string, KMRef>:
 *  key：commit
 *  value：ref 结构体
 */
map<string, KMRef> KMOSTreeHandler::getLocalRefsDeployed(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;

        // Handle refCompleted: deploy/runtime/top.openkylin.Platform/x86_64/0.5
        list<string> listStr = KMUtil::split(refspec, '/');
        if (listStr.size() != 5)
        {
            continue;
        }

        listStr.pop_front(); // drop "deploy"
        ref.kind = listStr.front();
        listStr.pop_front();
        // kmlogger.info("ref.kind: %s", ref.kind.c_str());

        ref.id = listStr.front();
        listStr.pop_front();
        ref.arch = listStr.front();
        listStr.pop_front();
        ref.branch = listStr.front();
        listStr.pop_front();

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

/**
 * @brief 获取 remote 对应已安装并部署的所有 app 和 runtime
 *
 * 此接口比较准确判断当前软件包是否已成功安装 : 一切以 deploy/** 为准
 *
 * 对于更新来说,一般都是通过 <completedId, commit> 来进行匹配
 *
 * @param remoteName
 * @return map<string, string>:
 *  key：completedId
 *  value：commit
 */
map<string, string> KMOSTreeHandler::getLocalRefsDeployedMap(const string &remoteName)
{
    map<string, string> 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))
    {
        char *refspec = (char *)key;
        char *checksum = (char *)value;

        // Handle refCompleted: deploy/runtime/top.openkylin.Platform/x86_64/0.5
        list<string> listStr = KMUtil::split(refspec, '/');
        if (listStr.size() != 5)
        {
            continue;
        }

        int pos = strlen("deploy/");
        string completedId = string(refspec).substr(pos, strlen(refspec) - pos);
        kmlogger.info("ref completedId:%s commit:%s", completedId.c_str(), checksum);

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

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

    g_autoptr(GHashTable) table = NULL;
    g_clear_error(&d->m_error);

    // TODO: 当repo url 不对时，出现程序卡死，该函数调用不返回。解决方案为在线程中调用该函数，并增加超时时间来终止线程。
    if (!ostree_repo_remote_list_refs(d->m_ostreeRepo, remoteName.c_str(), &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, '/');
        // kmlogger.info("remote refs size: %d", listStr.size());

        if (listStr.size() != 4)
        {
            continue;
        }
        ref.kind = listStr.front();
        listStr.pop_front();
        ref.id = listStr.front();
        listStr.pop_front();
        ref.arch = listStr.front();
        listStr.pop_front();
        ref.branch = listStr.front();
        listStr.pop_front();
        // kmlogger.info("ref.kind: %s id:%s branch:%s checksum:%s", ref.kind.c_str(), ref.id.c_str(), ref.branch.c_str(), checksum);

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

bool KMOSTreeHandler::pullWithOptions(const string &remoteName, const string &ref, GVariant *opts, CallBack cb)
{
    initProgress();
    d->m_progressCallback = cb;
    globalQueuePull.push_back(ref);
    if (!ostree_repo_pull_with_options(d->m_ostreeRepo, remoteName.c_str(), opts, d->m_pullProgress, nullptr, &d->m_error))
    {
        KMError(d->m_error->message);
        ostree_async_progress_finish(d->m_pullProgress);
        return false;
    }

    ostree_async_progress_finish(d->m_pullProgress);
    return true;
}

bool KMOSTreeHandler::fixPermissions(const std::string &path)
{
    struct stat st;
    if (stat(path.c_str(), &st) != 0)
    {
        std::cerr << "Error: Could not stat " << path << std::endl;
        return false;
    }

    // 如果权限包含 setgid 位，则修复权限
    if (st.st_mode & S_ISGID)
    {
        std::cout << "Fixing permissions for: " << path << std::endl;
        mode_t new_mode = st.st_mode & ~S_ISGID; // 去除 setgid 位
        if (chmod(path.c_str(), new_mode) != 0)
        {
            std::cerr << "Error: Could not fix permissions for " << path << std::endl;
            return false;
        }
        std::cout << "Permissions fixed for: " << path << std::endl;
    }
    return true;
}

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

    for (auto ref : vecRefs)
    {
        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)
        {
            KMError(d->m_error->message);
            return false; 
        }
    }
    ostree_async_progress_finish(d->m_pullProgress);
    return true;
}

bool KMOSTreeHandler::deleteRefTag(const string &remoteName, const string &ref, const string &commit)
{
    GError *err = nullptr;
    if (!ostree_repo_set_ref_immediate(d->m_ostreeRepo, remoteName != "" ? remoteName.c_str() : nullptr, ref.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);

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

    return true;
}

string KMOSTreeHandler::checksumFromBytes(const u_char *sha265_bytes)
{
    return string(ostree_checksum_from_bytes(sha265_bytes));
}

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);
}

bool KMOSTreeHandler::checkCommit(const string &commit)
{
    if (d->m_mapCommitInfo.find(commit) != d->m_mapCommitInfo.end())
    {
        return true;
    }
    return loadCommit(commit);
}

/**
 * @brief 获取collection_id
 * @param remote
 * @param collectionId
 * @return true 成功 false 失败
 */
bool KMOSTreeHandler::getCollectionId(const string &remote, string &collectionId)
{
    char *pCollectionId = nullptr;
    GError *error = nullptr;

    if (d->m_ostreeRepo == nullptr)
    {
        return false;
    }

    if (!ostree_repo_get_remote_option(d->m_ostreeRepo, remote.c_str(), "collection-id", nullptr, &pCollectionId, &error))
    {
        KMError("Remote " + remote + " does not have a collection ID set");
        return false;
    }

    if (pCollectionId == nullptr)
        return false;

    collectionId = pCollectionId;
    g_free(pCollectionId);

    return true;
}

/**
 * @brief 是否是extra-data
 * @return bool ture 是 false 否
 */
bool KMOSTreeHandler::isExtraData(const string &commit)
{
    if (d->m_ostreeRepo == nullptr)
    {
        return false;
    }

    g_autoptr(GVariant) extraDataSources = nullptr;
    g_autoptr(GVariant) commitv = nullptr;
    if (!ostree_repo_load_variant(d->m_ostreeRepo, OSTREE_OBJECT_TYPE_COMMIT, commit.c_str(), &commitv, nullptr))
    {
        return false;
    }

    g_autoptr(GVariant) commitMetadata = nullptr;
    commitMetadata = g_variant_get_child_value(commitv, 0);
    extraDataSources = g_variant_lookup_value(commitMetadata, "xa.extra-data-sources", G_VARIANT_TYPE("a(ayttays)"));
    if (extraDataSources == nullptr)
    {
        return false;
    }

    GVariant *pExtraDataSources = KMUtil::stealPointer(&extraDataSources);
    if (pExtraDataSources != nullptr && g_variant_n_children(pExtraDataSources) > 0)
    {
        return true;
    }

    return false;
}

/**
 * @brief 设置collection—_id
 * @param remote 名称
 * @param collectionId collection_id名称
 * @return bool ture 是 false 否
 */
bool KMOSTreeHandler::setCollectionId(const string &remote, const string &collectionId)
{
    if (d->m_ostreeRepo == nullptr)
    {
        return false;
    }

    GError *error = nullptr;
    g_autofree char *group = g_strdup_printf("remote \"%s\"", remote.c_str());
    GKeyFile *config = ostree_repo_copy_config(d->m_ostreeRepo);
    g_key_file_set_string(config, group, "collection-id", collectionId.c_str());
    if (!ostree_repo_write_config(d->m_ostreeRepo, config, &error))
    {
        return false;
    }

    return true;
}