#pragma once

#include <atomic>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

#ifndef _WIN32
#include <unistd.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <crypto/sha.h>
#include <paritypp/server.h>
#include <paritypp/client.h>
#include <paritypp/auth.h>
#include <paritypp/protocol.h>

namespace mediadb {

struct ClusterNode {
    std::string address;
    int         port;
};

struct ClusterConfig {
    bool                     enabled = false;
    std::vector<ClusterNode> peers;
    std::string              bind_address = "0.0.0.0";
    int                      port = 4433;
    std::string              cert_file;
    std::string              key_file;
    std::string              client_name;
    std::string              client_key;
    std::string              store_path;
    size_t                   data_blocks  = 2;
    size_t                   parity_blocks = 1;
};

// Deterministic group_id from a string key (e.g. "index" or store_id)
static inline uint64_t cluster_group_id(const std::string& key) {
    std::vector<uint8_t> input(key.begin(), key.end());
    auto hash = netplus::sha256_hash(input);
    uint64_t id = 0;
    for (int i = 0; i < 8; i++)
        id = (id << 8) | hash[i];
    return id;
}

class Cluster {
public:
    Cluster() = default;
    ~Cluster() { stop(); }

    void init(const ClusterConfig& cfg);
    void start();
    void stop();

    bool isRunning() const { return running_; }

    paritypp::block_store& getStore() { return *store_; }
    std::unique_ptr<paritypp::client>& getClient() { return pclient_; }
    std::timed_mutex& getClientMutex() { return client_mutex_; }
    const ClusterConfig& getConfig() const { return cfg_; }
    int getSelfNodeIndex() const { return self_node_index_; }
    size_t getTotalPeers() const { return cfg_.peers.size(); }
    const std::unordered_map<size_t, size_t>& getPeerToClientIndex() const { return peer_to_client_index_; }

    struct NodeGroupInfo {
        size_t index;
        std::string address;
        int port;
        bool online;
        std::vector<uint64_t> groups;
    };
    std::vector<NodeGroupInfo> list_peer_groups();

    bool replicate(const std::string& key, const uint8_t* data, size_t len);
    std::unique_ptr<paritypp::store_session> begin_replicate(const std::string& key, size_t total_size);
    bool fetch(const std::string& key, std::vector<uint8_t>& out);
    bool fetch_from_peers(const std::string& key, std::vector<uint8_t>& out);
    void remove(const std::string& key);

    // Compact the local block store file, reclaiming space from tombstoned records.
    bool vacuum_local_store();

    // Hold client lock across multiple operations (e.g. entire store import)
    // to avoid per-media lock acquire/release overhead.
    // replicate_unlocked / begin_replicate_unlocked assume lock is already held.
    std::unique_lock<std::timed_mutex> acquire_client_lock(int timeout_secs = 120);
    bool replicate_unlocked(const std::string& key, const uint8_t* data, size_t len);
    std::unique_ptr<paritypp::store_session> begin_replicate_unlocked(const std::string& key, size_t total_size);

private:
    ClusterConfig cfg_;
    std::atomic<bool> running_{false};
    std::thread server_thread_;

    std::shared_ptr<paritypp::block_store> store_;
    std::shared_ptr<paritypp::auth_store> auth_;
    std::unique_ptr<paritypp::server> server_;

    std::unique_ptr<paritypp::client> pclient_;
    std::timed_mutex client_mutex_;
    int self_node_index_ = -1;
    std::unordered_map<size_t, size_t> peer_to_client_index_;  // cfg peer idx -> pclient node idx

    void server_loop();
};

// Global cluster instance (nullptr when cluster is disabled)
extern Cluster* g_Cluster;

} // namespace mediadb
