/*
 * SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd.
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#pragma once

#include "linglong/api/types/v1/PackageInfoV2.hpp"
#include "linglong/api/types/v1/Repo.hpp"
#include "linglong/api/types/v1/RepoConfigV2.hpp"
#include "linglong/package/fuzzy_reference.h"
#include "linglong/package/layer_dir.h"
#include "linglong/package/reference.h"
#include "linglong/package_manager/package_task.h"
#include "linglong/repo/client_factory.h"
#include "linglong/repo/config.h"
#include "linglong/repo/remote_packages.h"
#include "linglong/repo/repo_cache.h"
#include "linglong/utils/error/error.h"

#include <ostree.h>

#include <QDir>

#include <filesystem>
#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace linglong::repo {

struct clearReferenceOption
{
    bool forceRemote = false;      // force clear remote reference
    bool fallbackToRemote = true;  // fallback to remote if local not found
    bool semanticMatching = false; // semantic matching compatible version
};

class RefMetaData
{
public:
    RefMetaData(const std::string_view &rev)
        : rev(rev)
    {
    }

    RefMetaData(const std::string_view &rev, const std::string_view &packageInfo)
        : rev(rev)
        , packageInfoContent(packageInfo)
    {
    }

    utils::error::Result<api::types::v1::PackageInfoV2> getPackageInfo() const noexcept;

    std::string getRev() const noexcept { return rev; }

private:
    std::string rev;
    std::string packageInfoContent;
};

struct RefStatistics
{
    uint64_t archived;
    uint64_t unpacked;
    uint64_t objects;
    uint64_t needed_archived;
    uint64_t needed_unpacked;
    uint64_t needed_objects;
};

class OSTreeRepo : public QObject

{
    Q_OBJECT
public:
    OSTreeRepo(const OSTreeRepo &) = delete;
    OSTreeRepo(OSTreeRepo &&) = delete;
    OSTreeRepo &operator=(const OSTreeRepo &) = delete;
    OSTreeRepo &operator=(OSTreeRepo &&) = delete;

    // Load existing repo from path, config is loaded from config.yaml
    // Opens existing ostree repo and cache
    // Returns error if any step fails
    static utils::error::Result<std::unique_ptr<OSTreeRepo>>
    loadFromPath(std::filesystem::path repoRoot) noexcept;

    // load or create repo with explicit config
    // Tries to open existing ostree repo, creates if load failed
    static utils::error::Result<std::unique_ptr<OSTreeRepo>>
    create(std::filesystem::path repoRoot, api::types::v1::RepoConfigV2 cfg) noexcept;

    ~OSTreeRepo() override;

    [[nodiscard]] const api::types::v1::RepoConfigV2 &getConfig() const noexcept;
    [[nodiscard]] api::types::v1::RepoConfigV2 getOrderedConfig() const noexcept;
    [[nodiscard]] utils::error::Result<api::types::v1::Repo>
    getRepoByAlias(const std::string &alias) const noexcept;
    [[nodiscard]] virtual std::vector<std::vector<api::types::v1::Repo>>
    getPriorityGroupedRepos() const noexcept;
    utils::error::Result<void> setConfig(const api::types::v1::RepoConfigV2 &cfg) noexcept;

    utils::error::Result<package::LayerDir>
    importLayerDir(const package::LayerDir &dir,
                   std::vector<std::filesystem::path> overlays = {},
                   const std::optional<std::string> &subRef = std::nullopt) noexcept;

    [[nodiscard]] utils::error::Result<package::LayerDir>
    getLayerDir(const package::Reference &ref,
                const std::string &module = "binary",
                const std::optional<std::string> &subRef = std::nullopt) const noexcept;
    [[nodiscard]] utils::error::Result<void>
    push(const package::Reference &reference, const std::string &module = "binary") const noexcept;

    [[nodiscard]] utils::error::Result<void>
    pushToRemote(const std::string &remoteRepo,
                 const std::string &url,
                 const package::Reference &reference,
                 const std::string &module = "binary") const noexcept;
    [[nodiscard]] utils::error::Result<void> pull(service::Task &taskContext,
                                                  const package::ReferenceWithRepo &refRepo,
                                                  const std::string &module) noexcept;

    [[nodiscard]] virtual utils::error::Result<package::Reference>
    clearReference(const package::FuzzyReference &fuzzy,
                   const clearReferenceOption &opts,
                   const std::string &module = "binary",
                   const std::optional<std::string> &repo = std::nullopt) const noexcept;

    virtual utils::error::Result<std::vector<api::types::v1::PackageInfoV2>>
    listLocal() const noexcept;
    utils::error::Result<std::vector<api::types::v1::PackageInfoV2>> virtual searchRemote(
      std::string searching, const api::types::v1::Repo &repo) const noexcept;
    utils::error::Result<std::vector<api::types::v1::PackageInfoV2>> virtual searchRemote(
      const package::FuzzyReference &fuzzyRef,
      const api::types::v1::Repo &repo,
      bool semanticMatching = false) const noexcept;
    utils::error::Result<repo::RemotePackages> virtual matchRemoteByPriority(
      const package::FuzzyReference &fuzzyRef,
      const std::optional<api::types::v1::Repo> &repo = std::nullopt) const noexcept;

    utils::error::Result<std::vector<api::types::v1::RepositoryCacheLayersItem>>
    listLayerItem() const noexcept;
    [[nodiscard]] virtual utils::error::Result<
      std::vector<api::types::v1::RepositoryCacheLayersItem>>
    listLocalBy(const linglong::repo::repoCacheQuery &query) const noexcept;
    utils::error::Result<int64_t>
    getLayerCreateTime(const api::types::v1::RepositoryCacheLayersItem &item) const noexcept;
    utils::error::Result<void>
    remove(const package::Reference &ref,
           const std::string &module = "binary",
           const std::optional<std::string> &subRef = std::nullopt) noexcept;
    utils::error::Result<void>
    remove(const api::types::v1::RepositoryCacheLayersItem &item) noexcept;

    utils::error::Result<void> prune();

    virtual utils::error::Result<RefMetaData>
    fetchRefMetaData(const package::ReferenceWithRepo &refRepo,
                     const std::string &module = "binary",
                     bool fetchPackageInfo = false) noexcept;
    virtual utils::error::Result<RefStatistics>
    getRefStatistics(const RefMetaData &meta) const noexcept;

    // exportReference should be called when LayerDir of ref is existed in local repo
    void exportReference(const package::Reference &ref) noexcept;
    // unexportReference should be called when LayerDir of ref is existed in local repo
    void unexportReference(const package::Reference &ref) noexcept;
    void unexportReference(const std::string &layerDir) noexcept;
    void updateSharedInfo() noexcept;
    utils::error::Result<void>
    markDeleted(const package::Reference &ref,
                bool deleted,
                const std::string &module = "binary",
                const std::optional<std::string> &subRef = std::nullopt) noexcept;
    bool isMarkedDeleted(const package::Reference &ref, const std::string &module) const noexcept;

    // 扫描layers变动，重新合并变动layer的modules
    [[nodiscard]] virtual utils::error::Result<void> mergeModules() const noexcept;
    // 获取合并后的layerDir，如果没有找到则返回binary模块的layerDir
    [[nodiscard]] virtual utils::error::Result<package::LayerDir>
    getMergedModuleDir(const package::Reference &ref,
                       bool fallbackLayerDir = true,
                       const std::optional<std::string> &subRef = std::nullopt) const noexcept;
    // 将指定的modules合并到临时目录，并返回合并后的layerDir，供打包者调试应用
    // 临时目录由调用者负责删除
    [[nodiscard]] virtual utils::error::Result<package::LayerDir> createTempMergedModuleDir(
      const package::Reference &ref, const std::vector<std::string> &modules) const noexcept;
    virtual std::vector<std::string> getModuleList(const package::Reference &ref) const noexcept;
    [[nodiscard]] virtual utils::error::Result<std::vector<std::string>> getRemoteModuleList(
      const package::Reference &ref, const api::types::v1::Repo &repo) const noexcept;
    virtual utils::error::Result<package::ReferenceWithRepo>
    latestRemoteReference(const package::FuzzyReference &fuzzyRef) const noexcept;
    virtual utils::error::Result<package::Reference>
    latestLocalReference(const package::FuzzyReference &fuzzyRef) const noexcept;

    [[nodiscard]] virtual utils::error::Result<api::types::v1::RepositoryCacheLayersItem>
    getLayerItem(const package::Reference &ref,
                 std::string module = "binary",
                 const std::optional<std::string> &subRef = std::nullopt) const noexcept;
    utils::error::Result<void> fixExportAllEntries() noexcept;
    [[nodiscard]] std::filesystem::path resolveEntryExportPath(
      const std::filesystem::path &relativePath, bool preferLibSystemdUser = false) const noexcept;
    [[nodiscard]] std::filesystem::path
    resolveDesktopFileExportPath(const std::filesystem::path &relativePath) const noexcept;

    virtual std::unique_ptr<ClientAPIWrapper> createClientV2(const std::string &url) const
    {
        return ClientFactory(url).createClientV2();
    }

    const api::types::v1::Repo &getDefaultRepo() const
    {
        return linglong::repo::getDefaultRepo(cfg);
    }

    virtual utils::error::Result<std::vector<api::types::v1::PackageInfoV2>>
    listLocalApps() const noexcept;
    utils::error::Result<std::vector<std::pair<package::Reference, package::ReferenceWithRepo>>>
    upgradableApps() const noexcept;

private:
    api::types::v1::RepoConfigV2 cfg;

    struct OstreeRepoDeleter
    {
        void operator()(OstreeRepo *repo) { g_clear_object(&repo); }
    };

    std::unique_ptr<OstreeRepo, OstreeRepoDeleter> ostreeRepo = nullptr;
    std::filesystem::path repoDir;
    std::unique_ptr<linglong::repo::RepoCache> cache{ nullptr };

    utils::error::Result<void> updateConfig(const api::types::v1::RepoConfigV2 &newCfg) noexcept;
    std::filesystem::path ostreeRepoDir() const noexcept;
    std::filesystem::path cacheFilePath() const noexcept;
    std::filesystem::path configFilePath() const noexcept;
    [[nodiscard]] utils::error::Result<QDir>
    ensureEmptyLayerDir(const std::string &commit) const noexcept;
    utils::error::Result<void> handleRepositoryUpdate(
      QDir layerDir, const api::types::v1::RepositoryCacheLayersItem &layer) noexcept;
    utils::error::Result<void>
    removeOstreeRef(const api::types::v1::RepositoryCacheLayersItem &layer) noexcept;
    utils::error::Result<void>
    undeployedLayer(const api::types::v1::RepositoryCacheLayersItem &layer) noexcept;
    [[nodiscard]] utils::error::Result<package::LayerDir>
    getLayerDir(const api::types::v1::RepositoryCacheLayersItem &layer) const noexcept;

    // 获取合并后的layerDir，如果没有找到则返回binary模块的layerDir
    [[nodiscard]] utils::error::Result<package::LayerDir>
    getMergedModuleDir(const api::types::v1::RepositoryCacheLayersItem &layer,
                       bool fallbackLayerDir = true) const noexcept;
    static utils::error::Result<void> IniLikeFileRewrite(const QFileInfo &info,
                                                         const QString &id) noexcept;

    // exportEntries will clear the entries/share and export all applications to the entries/share
    utils::error::Result<void> exportAllEntries() noexcept;
    utils::error::Result<std::vector<guint64>> getCommitSize(const std::string &remote,
                                                             const std::string &refString) noexcept;
    GVariantBuilder initOStreePullOptions(const std::string &ref) noexcept;

protected:
    OSTreeRepo(std::filesystem::path path, api::types::v1::RepoConfigV2 cfg) noexcept;

    utils::error::Result<void> init(bool create) noexcept;
    utils::error::Result<void> initCache(bool create) noexcept;

    // entries目录，/var/lib/linglong/entries
    std::filesystem::path getEntriesDir() const noexcept;
    // 默认的shared目录，/var/lib/linglong/entries/share
    std::filesystem::path getDefaultSharedDir() const noexcept;
    // 能覆盖系统目录的shared目录，/var/lib/linglong/entries/apps/share
    virtual std::filesystem::path getOverlayShareDir() const noexcept;
    utils::error::Result<void> exportDir(const std::string &appID,
                                         const std::filesystem::path &source,
                                         const std::filesystem::path &destination,
                                         const int &max_depth);
    utils::error::Result<void> exportEntries(
      const std::filesystem::path &, const api::types::v1::RepositoryCacheLayersItem &) noexcept;
};

} // namespace linglong::repo
