#pragma once
///@file

#include "nix/util/compression-settings.hh"
#include "nix/store/store-api.hh"
#include "nix/store/log-store.hh"

#include "nix/util/pool.hh"

#include <atomic>

namespace nix {

struct NarInfo;
class RemoteFSAccessor;

struct BinaryCacheStoreConfig : virtual StoreConfig
{
private:
    void anchor() override;

public:
    BinaryCacheStoreConfig(const Params & params)
        : StoreConfig(params, FilePathType::Unix)
    {
    }

    Setting<CompressionAlgo> compression{
        this,
        CompressionAlgo::xz,
        "compression",
        R"(
          NAR compression method. One of: `xz`, `bzip2`, `gzip`, `zstd`, `none`, `br`, `compress`, `grzip`, `lrzip`, `lz4`, `lzip`, `lzma` or `lzop`.
          To use a particular compression method Nix has to be built with a version of libarchive that natively supports that compression algorithm.
        )"};

    Setting<bool> writeNARListing{
        this, false, "write-nar-listing", "Whether to write a JSON file that lists the files in each NAR."};

    Setting<bool> writeDebugInfo{
        this,
        false,
        "index-debug-info",
        R"(
          Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to
          fetch debug info on demand
        )"};

    Setting<std::optional<AbsolutePath>> secretKeyFile{
        this, std::nullopt, "secret-key", "Path to the secret key used to sign the binary cache."};

    Setting<std::string> secretKeyFiles{
        this, "", "secret-keys", "List of comma-separated paths to the secret keys used to sign the binary cache."};

    Setting<std::optional<AbsolutePath>> localNarCache{
        this,
        std::nullopt,
        "local-nar-cache",
        "Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."};

    Setting<bool> parallelCompression{
        this,
        false,
        "parallel-compression",
        R"(
          Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`.

          If not set explicitly, defaults to `true` when `compression` is `zstd` and `false` otherwise.
        )"};

    Setting<int> compressionLevel{
        this,
        -1,
        "compression-level",
        R"(
          The *preset level* to be used when compressing NARs.
          The meaning and accepted values depend on the compression method selected.
          `-1` specifies that the default compression level should be used.
        )"};
};

/**
 * @note subclasses must implement at least one of the two
 * virtual getFile() methods.
 */
struct alignas(8) /* Work around ASAN failures on i686-linux. */
    BinaryCacheStore : virtual Store,
                       virtual LogStore
{
    using Config = BinaryCacheStoreConfig;

    /**
     * Intentionally mutable because some things we update due to the
     * cache's own (remote side) settings.
     */
    Config & config;

private:
    void anchor() override;

    std::vector<std::unique_ptr<Signer>> signers;

protected:

    /**
     * The prefix under which realisation infos will be stored
     *
     * @note The previous (still experimental, though) hash-keyed
     * realisations were under "realisations". "build trace" is a better
     * name anyways (issue #11895). This is call "v2" accordingly.
     *
     * While we're experimenting, we'll freely increase this version
     * number. Old build traces will just be "abandoned" at the old URL.
     * When we are done experimenting, we'll try lean more on versioning
     * the build trace entries themselves than the entire directory, for
     * a smoother migration path.
     */
    constexpr const static std::string realisationsPrefix = "build-trace-v2";

    constexpr const static std::string cacheInfoFile = "nix-cache-info";

    BinaryCacheStore(Config &);

    /**
     * Compute the path to the given realisation
     *
     * It's `${realisationsPrefix}/${drvPath}/${outputName}`.
     */
    std::string makeRealisationPath(const DrvOutput & id);

public:

    virtual bool fileExists(const std::string & path) = 0;

    virtual void upsertFile(
        const std::string & path, RestartableSource & source, const std::string & mimeType, uint64_t sizeHint) = 0;

    void upsertFile(
        const std::string & path,
        // FIXME: use std::string_view
        std::string && data,
        const std::string & mimeType,
        uint64_t sizeHint);

    void upsertFile(
        const std::string & path,
        // FIXME: use std::string_view
        std::string && data,
        const std::string & mimeType)
    {
        auto size = data.size();
        upsertFile(path, std::move(data), mimeType, size);
    }

    /**
     * Dump the contents of the specified file to a sink.
     */
    virtual void getFile(const std::string & path, Sink & sink);

    /**
     * Get the contents of /nix-cache-info. Return std::nullopt if it
     * doesn't exist.
     */
    virtual std::optional<std::string> getNixCacheInfo();

    /**
     * Fetch the specified file and call the specified callback with
     * the result. A subclass may implement this asynchronously.
     */
    virtual void getFile(const std::string & path, Callback<std::optional<std::string>> callback) noexcept;

    std::optional<std::string> getFile(const std::string & path);

public:

    virtual void init() override;

private:

    std::string narMagic;

    std::string narInfoFileFor(const StorePath & storePath);

    void writeNarInfo(ref<NarInfo> narInfo);

    /**
     * Upload the NAR for a path and everything else *except* the
     * `.narinfo` file (i.e. the compressed NAR, an optional NAR
     * listing, and optional debuginfo links), and construct the
     * corresponding `NarInfo`. The returned `NarInfo` is neither signed
     * nor published yet; call `uploadNarInfo()` to do that.
     */
    ref<NarInfo> uploadData(Source & narSource, RepairFlag repair, fun<ValidPathInfo(HashResult)> mkInfo);

    /**
     * Sign and publish the `.narinfo` file for a path whose NAR has
     * already been uploaded by `uploadData()`. This is what establishes
     * the closure invariant, so all of the path's references must
     * already be valid in the store.
     */
    void uploadNarInfo(ref<NarInfo> narInfo);

    ref<const ValidPathInfo> addToStoreCommon(
        Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs, fun<ValidPathInfo(HashResult)> mkInfo);

    /**
     * Same as `getFSAccessor`, but with a more preceise return type.
     */
    ref<RemoteFSAccessor> getRemoteFSAccessor(bool requireValidPath = true);

public:

    bool isValidPathUncached(const StorePath & path) override;

    void queryPathInfoUncached(
        const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;

    std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;

    void
    addToStore(const ValidPathInfo & info, Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs) override;

    void
    addMultipleToStore(PathsSource && pathsToCopy, Activity & act, RepairFlag repair, CheckSigsFlag checkSigs) override;

    StorePath addToStoreFromDump(
        Source & dump,
        std::string_view name,
        FileSerialisationMethod dumpMethod,
        ContentAddressMethod hashMethod,
        HashAlgorithm hashAlgo,
        const StorePathSet & references,
        RepairFlag repair) override;

    StorePath addToStore(
        std::string_view name,
        const SourcePath & path,
        ContentAddressMethod method,
        HashAlgorithm hashAlgo,
        const StorePathSet & references,
        PathFilter & filter,
        RepairFlag repair) override;

    void registerDrvOutput(const Realisation & info) override;

    void queryRealisationUncached(
        const DrvOutput &, Callback<std::shared_ptr<const UnkeyedRealisation>> callback) noexcept override;

    void narFromPath(const StorePath & path, Sink & sink) override;

    ref<SourceAccessor> getFSAccessor(bool requireValidPath = true) override;

    std::shared_ptr<SourceAccessor> getFSAccessor(const StorePath &, bool requireValidPath = true) override;

    void addSignatures(const StorePath & storePath, const std::set<Signature> & sigs) override;

    std::optional<std::string> getBuildLogExact(const StorePath & path) override;

    void addBuildLog(const StorePath & drvPath, std::string_view log) override;
};

MakeError(NoSuchBinaryCacheFile, Error);

} // namespace nix
