#pragma once

#include "types.h"
#include "lru_cache.h"

#include <atomic>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <set>
#include <shared_mutex>
#include <thread>
#include <unordered_map>

namespace mediadb {

class Cluster;

// ---- Streaming replicate session (fed with raw data chunks) ----

class ReplicateSession {
public:
    virtual ~ReplicateSession() = default;
    virtual void feed(const uint8_t* data, size_t len) = 0;
    virtual void finalize() = 0;
    virtual bool ok() const = 0;
    virtual std::string error_message() const { return {}; }
};

// Callback types for replication
using ReplicateFn = std::function<bool(const std::string&, const uint8_t*, size_t)>;
using BeginReplicateFn = std::function<std::unique_ptr<ReplicateSession>(const std::string&, size_t)>;

// ---- Incremental import session (fed with network chunks) ----

class ImportSession {
public:
    virtual ~ImportSession() = default;
    virtual void feed(const char* data, size_t len) = 0;
    virtual bool done() const = 0;
    virtual bool ok() const = 0;
    virtual std::string error_message() const { return {}; }
    // Take deferred cluster ops without executing them (caller dispatches).
    virtual std::vector<std::function<void()>> take_deferred() { return {}; }
};

// ---- Abstract backend interface ----

class MediaBackendApi {
public:
    virtual ~MediaBackendApi() = default;

    // incremental import
    virtual std::unique_ptr<ImportSession> begin_import() = 0;

    // stores
    virtual StoreRecord create_store(const std::string& name) = 0;
    virtual std::optional<StoreRecord> get_store(const std::string& id) const = 0;
    virtual std::vector<StoreRecord> list_stores() const = 0;
    virtual bool delete_store(const std::string& id) = 0;
    virtual bool rename_store(const std::string& id, const std::string& new_name) = 0;

    // store auth domains
    virtual std::string add_store_domain(const std::string& store_id, AuthDomain domain) = 0;
    virtual bool remove_store_domain(const std::string& store_id, const std::string& domain_id) = 0;
    virtual std::vector<AuthDomain> list_store_domains(const std::string& store_id) const = 0;
    virtual std::optional<AuthDomain> find_domain_by_label(const std::string& label) const = 0;

    // store ACLs
    virtual std::string add_store_acl(const std::string& store_id, StoreACL acl) = 0;
    virtual bool remove_store_acl(const std::string& store_id, const std::string& acl_id) = 0;
    virtual std::vector<StoreACL> list_store_acls(const std::string& store_id) const = 0;
    virtual std::vector<std::string> all_acl_group_ids() const = 0;

    // albums
    virtual std::optional<AlbumRecord> create_album(const std::string& store_id, const std::string& name) = 0;
    virtual std::optional<AlbumRecord> get_album(const std::string& id) const = 0;
    virtual std::vector<AlbumRecord> list_albums(const std::string& store_id) const = 0;
    virtual bool delete_album(const std::string& id) = 0;
    virtual bool set_album_public(const std::string& album_id, bool is_public) = 0;
    virtual bool is_album_public(const std::string& album_id) const = 0;
    virtual bool is_media_public(const std::string& media_id) const = 0;
    virtual std::vector<AlbumRecord> list_public_albums() const = 0;

    // media
    virtual std::optional<MediaRecord> add_media(const std::string& album_id,
                                                  const std::string& filename,
                                                  const std::string& content_type,
                                                  std::vector<std::uint8_t> data) = 0;
    virtual std::optional<MediaRecord> get_media(const std::string& id) const = 0;
    virtual std::vector<MediaRecord> list_media(const std::string& album_id) const = 0;
    virtual bool delete_media(const std::string& id) = 0;

    // raw data access
    virtual std::vector<std::uint8_t> read_media_data(const std::string& media_id) const = 0;
    virtual std::vector<std::uint8_t> read_media_data_range(const std::string& media_id,
                                                             std::uint64_t offset,
                                                             std::uint64_t length) const = 0;
    virtual std::uint64_t get_media_size(const std::string& media_id) const = 0;

    // export / import
    virtual bool export_db(const std::string& path) const = 0;
    virtual std::vector<std::uint8_t> export_db_to_buffer() const = 0;
    virtual bool import_db(const std::string& path) = 0;
    virtual bool import_db_from_buffer(const std::vector<std::uint8_t>& data) = 0;
    virtual bool import_db_from_buffer(const std::uint8_t* data, std::size_t len) = 0;

    // vacuum
    virtual void vacuum_store(const std::string& store_id) = 0;
    virtual void vacuum_all_stores() = 0;
};

// ---- BinDb — persistent binary store (local only) ----

class BinDb : public MediaBackendApi {
public:
    explicit BinDb(const std::string& db_dir);
    // Memory-only mode: no disk I/O, data lives in pending_data
    BinDb();

    // stores
    StoreRecord create_store(const std::string& name) override;
    std::optional<StoreRecord> get_store(const std::string& id) const override;
    std::vector<StoreRecord> list_stores() const override;
    bool delete_store(const std::string& id) override;
    bool rename_store(const std::string& id, const std::string& new_name) override;

    // store auth domains
    std::string add_store_domain(const std::string& store_id, AuthDomain domain) override;
    bool remove_store_domain(const std::string& store_id, const std::string& domain_id) override;
    std::vector<AuthDomain> list_store_domains(const std::string& store_id) const override;
    std::optional<AuthDomain> find_domain_by_label(const std::string& label) const override;

    // store ACLs
    std::string add_store_acl(const std::string& store_id, StoreACL acl) override;
    bool remove_store_acl(const std::string& store_id, const std::string& acl_id) override;
    std::vector<StoreACL> list_store_acls(const std::string& store_id) const override;
    std::vector<std::string> all_acl_group_ids() const override;

    // albums
    std::optional<AlbumRecord> create_album(const std::string& store_id, const std::string& name) override;
    std::optional<AlbumRecord> get_album(const std::string& id) const override;
    std::vector<AlbumRecord> list_albums(const std::string& store_id) const override;
    bool delete_album(const std::string& id) override;
    bool set_album_public(const std::string& album_id, bool is_public) override;
    bool is_album_public(const std::string& album_id) const override;
    bool is_media_public(const std::string& media_id) const override;
    std::vector<AlbumRecord> list_public_albums() const override;

    // media
    std::optional<MediaRecord> add_media(const std::string& album_id,
                                         const std::string& filename,
                                         const std::string& content_type,
                                         std::vector<std::uint8_t> data) override;
    std::optional<MediaRecord> get_media(const std::string& id) const override;
    std::vector<MediaRecord> list_media(const std::string& album_id) const override;
    bool delete_media(const std::string& id) override;

    // raw data access
    std::vector<std::uint8_t> read_media_data(const std::string& media_id) const override;
    std::vector<std::uint8_t> read_media_data_range(const std::string& media_id,
                                                     std::uint64_t offset,
                                                     std::uint64_t length) const override;
    std::uint64_t get_media_size(const std::string& media_id) const override;

    // export / import
    bool export_db(const std::string& path) const override;
    std::vector<std::uint8_t> export_db_to_buffer() const override;
    bool import_db(const std::string& path) override;
    bool import_db_from_buffer(const std::vector<std::uint8_t>& data) override;
    bool import_db_from_buffer(const std::uint8_t* data, std::size_t len) override;
    bool import_db_from_buffer(const std::uint8_t* data, std::size_t len,
                               std::function<void(const std::string&, const uint8_t*, size_t)> on_replicate);

    // incremental import
    std::unique_ptr<ImportSession> begin_import() override;
    using StoreLifecycleFn = std::function<void()>;
    std::unique_ptr<ImportSession> begin_import(
        ReplicateFn on_replicate,
        BeginReplicateFn begin_replicate = nullptr,
        StoreLifecycleFn on_store_begin = nullptr,
        StoreLifecycleFn on_store_end = nullptr);

    // public utility (for cluster backend)
    fs::path index_file() const;
    fs::path store_file(const std::string& store_id) const;
    void reload();
    std::vector<std::string> store_ids() const;
    std::vector<std::string> media_ids() const;

    // buffer-based save/load (for cluster-only storage)
    std::vector<std::uint8_t> save_index_to_buffer() const;
    void load_index_from_buffer(const std::vector<std::uint8_t>& data);
    std::vector<std::uint8_t> save_store_to_buffer(const std::string& store_id);
    void load_store_from_buffer(const std::string& store_id, const std::vector<std::uint8_t>& data);
    bool is_memory_only() const { return memory_only_; }

    // metadata-only store buffer (no raw media data) for cluster replication
    std::vector<std::uint8_t> save_store_metadata_to_buffer(const std::string& store_id) const;
    void load_store_metadata_from_buffer(const std::string& store_id, const std::vector<std::uint8_t>& data);

    // add media record without storing raw data locally (cluster-only mode)
    std::optional<MediaRecord> add_media_metadata(const std::string& album_id,
                                                   const std::string& filename,
                                                   const std::string& content_type,
                                                   std::uint64_t size_bytes);

    // vacuum: rewrite store file, reclaiming space from deleted records
    void vacuum_store(const std::string& store_id) override;
    void vacuum_all_stores() override;

    friend class BinDbImportSession;

private:
    static constexpr char INDEX_MAGIC_V2[4] = {'M', 'D', 'B', '2'};
    static constexpr char INDEX_MAGIC_V3[4] = {'M', 'D', 'B', '3'};
    static constexpr char INDEX_MAGIC[4]  = {'M', 'D', 'B', '4'};
    static constexpr char STORE_MAGIC[4] = {'M', 'D', 'S', '1'};
    static constexpr char STORE_MAGIC_V2[4] = {'M', 'D', 'S', '2'};
    static constexpr char STORE_MAGIC_V3[4] = {'M', 'D', 'S', '3'};
    static constexpr char EXPORT_MAGIC[4] = {'M', 'D', 'E', '1'};
    static constexpr char EXPORT_MAGIC_V2[4] = {'M', 'D', 'E', '2'};

    static constexpr std::uint8_t REC_EMPTY = 0;
    static constexpr std::uint8_t REC_ALBUM = 1;
    static constexpr std::uint8_t REC_MEDIA = 2;

    static std::string detect_kind(std::string ext);

    void load();
    void load_index();
    void load_store(const std::string& store_id, StoreRecord& store);
    void save_index_locked() const;
    void save_store_locked(const std::string& store_id);
    void append_album_locked(const std::string& store_id, const AlbumRecord& album);
    void append_media_locked(const std::string& store_id, MediaRecord& media);
    void zero_record_locked(const std::string& store_id, std::uint64_t record_offset);
    void ensure_store_file_locked(const std::string& store_id);
    void vacuum_store_locked(const std::string& store_id);
    bool import_from_stream(std::istream& in);
    bool import_from_stream(std::istream& in,
                            std::function<void(const std::string&, const uint8_t*, size_t)> on_replicate);
    std::vector<std::uint8_t> save_store_to_buffer_locked(const std::string& store_id) const;
    std::vector<std::uint8_t> save_store_metadata_to_buffer_locked(const std::string& store_id) const;
    std::vector<std::uint8_t> save_index_to_buffer_locked() const;
    void mmap_store(const std::string& store_id);
    void mmap_all_stores();

    std::string db_dir_;
    bool memory_only_ = false;
    mutable std::shared_mutex mutex_;
    std::unordered_map<std::string, StoreRecord> stores_;
    std::unordered_map<std::string, AlbumRecord> albums_;
    std::unordered_map<std::string, MediaRecord> media_;
    std::unordered_map<std::string, MmapRegion> mmaps_;
};

// ---- Cluster backend: wraps BinDb and adds replication + periodic sync ----

class ClusterMediaBackend : public MediaBackendApi {
public:
    ClusterMediaBackend(Cluster& cluster, BlobCache& cache);
    ~ClusterMediaBackend() override;

    // stores
    StoreRecord create_store(const std::string& name) override;
    std::optional<StoreRecord> get_store(const std::string& id) const override;
    std::vector<StoreRecord> list_stores() const override;
    bool delete_store(const std::string& id) override;
    bool rename_store(const std::string& id, const std::string& new_name) override;

    // store auth domains
    std::string add_store_domain(const std::string& store_id, AuthDomain domain) override;
    bool remove_store_domain(const std::string& store_id, const std::string& domain_id) override;
    std::vector<AuthDomain> list_store_domains(const std::string& store_id) const override;
    std::optional<AuthDomain> find_domain_by_label(const std::string& label) const override;

    // store ACLs
    std::string add_store_acl(const std::string& store_id, StoreACL acl) override;
    bool remove_store_acl(const std::string& store_id, const std::string& acl_id) override;
    std::vector<StoreACL> list_store_acls(const std::string& store_id) const override;
    std::vector<std::string> all_acl_group_ids() const override;

    // albums
    std::optional<AlbumRecord> create_album(const std::string& store_id, const std::string& name) override;
    std::optional<AlbumRecord> get_album(const std::string& id) const override;
    std::vector<AlbumRecord> list_albums(const std::string& store_id) const override;
    bool delete_album(const std::string& id) override;
    bool set_album_public(const std::string& album_id, bool is_public) override;
    bool is_album_public(const std::string& album_id) const override;
    bool is_media_public(const std::string& media_id) const override;
    std::vector<AlbumRecord> list_public_albums() const override;

    // media
    std::optional<MediaRecord> add_media(const std::string& album_id,
                                          const std::string& filename,
                                          const std::string& content_type,
                                          std::vector<std::uint8_t> data) override;
    std::optional<MediaRecord> get_media(const std::string& id) const override;
    std::vector<MediaRecord> list_media(const std::string& album_id) const override;
    bool delete_media(const std::string& id) override;

    // raw data access
    std::vector<std::uint8_t> read_media_data(const std::string& media_id) const override;
    std::vector<std::uint8_t> read_media_data_range(const std::string& media_id,
                                                     std::uint64_t offset,
                                                     std::uint64_t length) const override;
    std::uint64_t get_media_size(const std::string& media_id) const override;

    // export / import
    bool export_db(const std::string& path) const override;
    std::vector<std::uint8_t> export_db_to_buffer() const override;
    bool import_db(const std::string& path) override;
    bool import_db_from_buffer(const std::vector<std::uint8_t>& data) override;
    bool import_db_from_buffer(const std::uint8_t* data, std::size_t len) override;

    // incremental import
    std::unique_ptr<ImportSession> begin_import() override;

    // vacuum
    void vacuum_store(const std::string& store_id) override;
    void vacuum_all_stores() override;

    // cluster sync
    void start_sync();
    void sync_from_cluster();
    void repair_index();
    BinDb& local();

private:
    void replicate_index(bool force = false);
    void replicate_store(const std::string& store_id);
    void replicate_tombstones();
    void load_tombstones();
    void apply_tombstones();
    std::vector<std::uint8_t> export_db_to_buffer_impl() const;
    void repair_replication();
    void sync_loop();

    BinDb local_;
    Cluster& cluster_;
    std::atomic<bool> sync_running_{false};
    std::atomic<bool> importing_{false};
    std::atomic<bool> initial_sync_ok_{false};
    std::thread sync_thread_;
    std::mutex sync_mutex_;
    std::condition_variable sync_cv_;
    mutable std::shared_mutex cluster_op_mutex_;  // protects write+replicate+read vs sync_from_cluster

    BlobCache& cache_;

    // Tombstones: store IDs that have been explicitly deleted.
    // Replicated as cluster key "tombstones" so all nodes know about deletions.
    std::set<std::string> tombstones_;

    std::vector<std::uint8_t> cache_fetch_range(const std::string& media_id,
                                                 std::uint64_t offset,
                                                 std::uint64_t length) const;
};

} // namespace mediadb
