#pragma once

#include "lru_cache.h"
#include "cluster.h"

#include <paritypp/client.h>
#include <paritypp/server.h>

#include <crypto/sha.h>

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace mediadb {

// Fragment size for large previews: 10 MB
static constexpr size_t PREVIEW_FRAGMENT_SIZE = 10ULL * 1024 * 1024;

// Cluster-shared preview cache (L2) with fragment support.
//
// Uses store_id=1 on the existing cluster parity server (same port).
// Local block_store is either:
//   - a memory_block_store with byte-budget LRU eviction (default, store_path empty)
//   - a PersistentLruBlockStore persisting to disk (when store_path is set),
//     applying the same byte-budget LRU eviction on top of a durable
//     file_block_store, so max_bytes_per_node is enforced either way.
//
// Small previews (≤ PREVIEW_FRAGMENT_SIZE) are stored as a single parity
// group holding content_type+etag+data together — one store()/retrieve()
// round trip. Large previews are split into 10 MB fragments, each in its
// own parity group, plus a metadata group describing the layout (an
// inherent N+1 round trips: metadata, then each chunk).
//
// L1 (BlobCache) sits in front — this class handles L1 miss → L2 lookup → render.
class PreviewCache {
public:
    // cluster: the existing Cluster instance (to register store + create client)
    // max_bytes_per_node: byte budget for the local block store (LRU-evicted
    //                     either way, in-memory or on-disk)
    // store_path: when non-empty, back L2 with a durable, LRU-evicted store
    //             rooted at this directory instead of an in-memory store
    PreviewCache(Cluster& cluster, size_t max_bytes_per_node,
                 const std::string& store_path = "");

    // Create client connections and warm up. Must be called post-fork.
    void start();

    // Try to retrieve a preview from L2.
    // Returns the assembled blob, or nullptr on miss.
    // On hit, also inserts fragment(s) into L1.
    std::shared_ptr<const BlobValue> get(const std::string& key, BlobCache& l1);

    // Store a rendered preview into L2 (and L1).
    // Automatically fragments if > PREVIEW_FRAGMENT_SIZE.
    void put(const std::string& key, const BlobValue& blob, BlobCache& l1);

private:
    // Deterministic group_id from a cache key string
    static uint64_t cache_group_id(const std::string& key);

    // Fragment metadata (large-preview case only; chunk payloads live in
    // separate "key:c:N" groups, so this record carries no data itself).
    struct FragMeta {
        uint32_t num_chunks;
        uint64_t total_size;
        std::string content_type;
        std::string etag;
    };

    // Every group written by put() (small-preview envelope or fragment
    // metadata) starts with [4B magic][1B kind] so get() can tell the two
    // apart — and reject any raw preview bytes that happen to have landed
    // in the same slot from a stale/incompatible format — without guessing.
    enum class EnvelopeKind : uint8_t { Small = 0, Fragmented = 1 };

    // [4B magic][1B kind=Fragmented][4B num_chunks][8B total_size][content_type\0][etag\0]
    static std::vector<uint8_t> encode_frag_meta(const FragMeta& m);
    static bool decode_frag_meta(const std::vector<uint8_t>& data, FragMeta& out);

    // [4B magic][1B kind=Small][8B data_len][content_type\0][etag\0][data...]
    // Bundles metadata and payload into one group — one store()/retrieve()
    // round trip per small preview, instead of a separate ":meta" group.
    static std::vector<uint8_t> encode_small(const std::string& content_type,
                                              const std::string& etag,
                                              const std::vector<uint8_t>& data);
    static bool decode_small(const std::vector<uint8_t>& data, std::string& content_type,
                              std::string& etag, std::vector<uint8_t>& payload);

    static constexpr uint8_t STORE_ID = 1; // preview cache store

    std::shared_ptr<paritypp::block_store> store_;
    std::unique_ptr<paritypp::client> client_;
};

} // namespace mediadb
