#pragma once

#include <cstddef>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>

namespace mediadb {

/*
 * LruByteCache — generic thread-safe LRU cache with byte-budget eviction.
 *
 *   Key   : lookup key type (must be hashable via Hash and equality-comparable)
 *   Value : stored value type (moved into a shared_ptr<const Value>)
 *   Hash  : hash functor for Key
 *   SizeFn: functor returning the byte size of a Value  (default: v.size())
 */
template<typename Key, typename Value, typename Hash,
         typename SizeFn = std::size_t(*)(const Value&)>
class LruByteCache {
public:
    explicit LruByteCache(std::size_t max_bytes, SizeFn size_fn)
        : max_bytes_(max_bytes), size_fn_(size_fn) {
        const std::size_t est = max_bytes / (64U * 1024U);
        if (est > 0) map_.reserve(est);
    }

    /* Look up an entry.  Returns nullptr on miss; promotes to MRU on hit. */
    std::shared_ptr<const Value> get(const Key& key) {
        std::lock_guard<std::mutex> lk(mutex_);
        auto it = map_.find(key);
        if (it == map_.end()) return nullptr;
        lru_.splice(lru_.begin(), lru_, it->second.lru_it);   // promote
        return it->second.value;
    }

    /* Insert or replace an entry; evicts LRU tail while over budget. */
    void put(const Key& key, Value val) {
        std::size_t sz = size_fn_(val);
        if (sz > max_bytes_) return;       // single entry exceeds budget

        std::lock_guard<std::mutex> lk(mutex_);

        // Remove existing entry for this key (if any)
        auto existing = map_.find(key);
        if (existing != map_.end()) {
            used_ -= size_fn_(*existing->second.value);
            lru_.erase(existing->second.lru_it);
            map_.erase(existing);
        }

        // Evict from tail until we have room
        while (!lru_.empty() && used_ + sz > max_bytes_) {
            auto& old_key = lru_.back();
            auto old_it = map_.find(old_key);
            if (old_it != map_.end()) {
                used_ -= size_fn_(*old_it->second.value);
                map_.erase(old_it);
            }
            lru_.pop_back();
        }

        lru_.push_front(key);
        map_[key] = Entry{std::make_shared<const Value>(std::move(val)),
                          lru_.begin()};
        used_ += sz;
    }

private:
    struct Entry {
        std::shared_ptr<const Value> value;
        typename std::list<Key>::iterator lru_it;
    };

    std::size_t       max_bytes_;
    std::size_t       used_ = 0;
    SizeFn            size_fn_;
    std::mutex        mutex_;
    std::list<Key>    lru_;
    std::unordered_map<Key, Entry, Hash> map_;
};

// ---- BlobCache: unified string-keyed cache for data blocks and previews ----

enum class BlobType : std::uint8_t { data, preview, prefetch };

struct BlobValue {
    std::vector<std::uint8_t> data;
    std::string content_type;
    std::string etag;
    BlobType type = BlobType::data;
};

inline std::size_t blob_value_size(const BlobValue& v) { return v.data.size(); }

using BlobCache = LruByteCache<std::string, BlobValue, std::hash<std::string>,
                                std::size_t(*)(const BlobValue&)>;

} // namespace mediadb
