// Copyright Contributors to the DNF5 project.
// Copyright Contributors to the libdnf project.
// SPDX-License-Identifier: LGPL-2.1-or-later
//
// This file is part of libdnf: https://github.com/rpm-software-management/libdnf/
//
// Libdnf 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.
//
// Libdnf 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 Lesser General Public License
// along with libdnf.  If not, see <https://www.gnu.org/licenses/>.

#ifndef LIBDNF5_REPO_REPO_DOWNLOADER_HPP
#define LIBDNF5_REPO_REPO_DOWNLOADER_HPP


#include "repo/download_data.hpp"
#include "repo/librepo.hpp"

#include "libdnf5/repo/repo_weak.hpp"
#include "libdnf5/utils/fs/temp.hpp"

namespace std {

template <>
struct default_delete<LrMetadataTarget> {
    void operator()(LrMetadataTarget * ptr) noexcept { lr_metadatatarget_free(ptr); }
};

}  // namespace std

namespace libdnf5::repo {

/// Handles downloading and loading of repository metadata.
/// @exception RepoDownloadError (public) All public methods should throw this exception,
///                                       under which a lower-level exception can often be nested.
class RepoDownloader {
public:
    using repo_loading_func = void(libdnf5::repo::Repo * repo, bool reusing);

    // Names of metadata files in rpm repository
    // Final metadata file name is (hash-) + this constant + ".xml" [+ compression suffix]
    static constexpr const char * MD_FILENAME_PRIMARY = "primary";
    static constexpr const char * MD_FILENAME_FILELISTS = "filelists";
    static constexpr const char * MD_FILENAME_PRESTODELTA = "prestodelta";
    static constexpr const char * MD_FILENAME_UPDATEINFO = "updateinfo";
    static constexpr const char * MD_FILENAME_OTHER = "other";
    static constexpr const char * MD_FILENAME_GROUP_GZ = "group_gz";
    static constexpr const char * MD_FILENAME_GROUP = "group";
    static constexpr const char * MD_FILENAME_MODULES = "modules";
    static constexpr const char * MD_FILENAME_APPSTREAM = "appstream";

    static bool is_metalink_in_sync(Repo & repo, LrMetalink * metalink);
    static bool is_repomd_in_sync(Repo & repo, std::filesystem::path repomd);

    static void load_local(DownloadData & download_data);
    static LibrepoHandle & get_cached_handle(Repo & repo);

    /// Adds repos for which metadata will be downloaded in parallel
    void add(libdnf5::repo::Repo & repo, const std::string & destdir, std::function<repo_loading_func> load_repo);

    // For each repo downloads metalink (or repomd if no metalink) and
    // checks with local files if the repo needs to be downloaded.
    // If the repo is determined to be in sync it won't be downloaded
    // during the download() call.
    // It returns a map with errors and a single bool signifying if
    // all the added repos are in sync or not.
    // NOTE(amatej): If the API should become public we might
    // want to change it so that its possible to tell which
    // repos will be downloaded in the following download() call.
    std::tuple<std::unordered_map<Repo *, std::vector<std::string>>, bool> download_repos_descriptions();

    // Download the previously added repos.
    std::unordered_map<Repo *, std::vector<std::string>> download();

private:
    struct CallbackData {
        std::function<repo_loading_func> load_repo;
        std::string destination;
        std::optional<libdnf5::utils::fs::TempDir> temp_download_target;
        void * user_cb_data{nullptr};
        double prev_total_to_download;
        double prev_downloaded;
        double sum_prev_downloaded;
        RepoWeakPtr repo;
        std::unique_ptr<LrMetadataTarget> lr_target;
        bool is_in_sync;
    };

    static LibrepoHandle init_local_handle(const DownloadData & download_data);
    static LibrepoHandle init_remote_handle(Repo & repo, const char * destdir, bool mirror_setup = true);
    static void common_handle_setup(LibrepoHandle & h, const DownloadData & download_data);
    static void apply_http_headers(DownloadData & download_data, LibrepoHandle & handle);
    static void add_countme_flag(DownloadData & download_data, LibrepoHandle & handle);
    static time_t get_system_epoch();
    static void configure_handle_dlist(LibrepoHandle & handle, std::set<std::string> && optional_metadata);

    static int end_cb_full_download(void * data, LrTransferStatus status, const char * msg);
    static int end_cb_sync_check(void * data, LrTransferStatus status, const char * msg);
    static int progress_cb(void * data, double total_to_download, double downloaded);
    static void fastest_mirror_cb(void * data, LrFastestMirrorStages stage, void * ptr);
    static int mirror_failure_cb(void * data, const char * msg, const char * url);
    static int mirror_failure_cb(void * data, const char * msg, const char * url, const char * metadata);

    std::vector<CallbackData> callback_data;
};

}  // namespace libdnf5::repo

#endif  // LIBDNF5_REPO_REPO_DOWNLOADER_HPP
