/*******************************************************************************
 * Copyright (c) 2026, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

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

#include "cluster.h"
#include "exception.h"

#include <netplus/exception.h>
#include <crypto/sha.h>

#include <cstring>
#include <iostream>
#include <algorithm>
#include <set>
#include <unordered_map>

#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

namespace authdb {

    Cluster *g_Cluster = nullptr;

    // --- SessionBlock serialization ---

    // Wire format:
    //   [4B magic 'SESB']
    //   [16B sid][16B uid][16B did]
    //   [4B member_count]
    //   [16B * member_count member uuids]
    //   [4B username_len]
    //   [username_len bytes username]
    //   [4B gpo_count]
    //   [for each GPO: 16B GPOId + 1B GPORes]

    static constexpr uint8_t SESSION_MAGIC[4] = {'S', 'E', 'S', 'B'};

    std::vector<uint8_t> SessionBlock::serialize(const SessionData &sess) {
        uuid::uuid sid, uid, did;
        sess.getSid(sid);
        sess.getUid(uid);
        sess.getDid(did);
        const char *username = sess.getUsername();
        size_t username_len = username ? strlen(username) : 0;

        // Collect members
        std::vector<uuid::uuid> members;
        sess.getMembers(members);

        // Collect GPO results
        std::vector<std::pair<uuid::uuid, bool>> gpo_results;
        sess.getGPOResults(gpo_results);

        // Magic(4) + Header: 16+16+16 + 4 + members*16 + 4 + username + 4 + gpos*17
        size_t total = 4 + 48 + 4 + (members.size() * 16) + 4 + username_len
                     + 4 + (gpo_results.size() * 17);

        std::vector<uint8_t> buf(total);
        size_t pos = 0;

        // Magic
        std::memcpy(buf.data() + pos, SESSION_MAGIC, 4);
        pos += 4;

        // SID
        std::memcpy(buf.data() + pos, sid.value, 16);
        pos += 16;

        // UID
        std::memcpy(buf.data() + pos, uid.value, 16);
        pos += 16;

        // DID
        std::memcpy(buf.data() + pos, did.value, 16);
        pos += 16;

        // Member count and UUIDs
        uint32_t mc = static_cast<uint32_t>(members.size());
        std::memcpy(buf.data() + pos, &mc, 4);
        pos += 4;

        for (const auto &m : members) {
            std::memcpy(buf.data() + pos, m.value, 16);
            pos += 16;
        }

        // Username length
        uint32_t ul = static_cast<uint32_t>(username_len);
        std::memcpy(buf.data() + pos, &ul, 4);
        pos += 4;

        // Username
        if (username_len > 0) {
            std::memcpy(buf.data() + pos, username, username_len);
            pos += username_len;
        }

        // GPO count and results
        uint32_t gc = static_cast<uint32_t>(gpo_results.size());
        std::memcpy(buf.data() + pos, &gc, 4);
        pos += 4;

        for (const auto &gpo : gpo_results) {
            std::memcpy(buf.data() + pos, gpo.first.value, 16);
            pos += 16;
            buf[pos] = gpo.second ? 1 : 0;
            pos += 1;
        }

        return buf;
    }

    bool SessionBlock::deserialize(const std::vector<uint8_t> &data,
                                    uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                                    std::vector<uuid::uuid> &members,
                                    std::string &username,
                                    std::vector<std::pair<uuid::uuid, bool>> &gpo_results) {
        if (data.size() < 60) return false;  // 4 magic + 48 uuids + 4 member_count + 4 username_len

        // Check magic
        if (std::memcmp(data.data(), SESSION_MAGIC, 4) != 0) return false;

        size_t pos = 4;

        std::memcpy(sid.value, data.data() + pos, 16);
        pos += 16;

        std::memcpy(uid.value, data.data() + pos, 16);
        pos += 16;

        std::memcpy(did.value, data.data() + pos, 16);
        pos += 16;

        uint32_t mc = 0;
        std::memcpy(&mc, data.data() + pos, 4);
        pos += 4;

        members.clear();
        for (uint32_t i = 0; i < mc; i++) {
            if (pos + 16 > data.size()) return false;
            uuid::uuid m;
            std::memcpy(m.value, data.data() + pos, 16);
            members.push_back(m);
            pos += 16;
        }

        if (pos + 4 > data.size()) return false;
        uint32_t ul = 0;
        std::memcpy(&ul, data.data() + pos, 4);
        pos += 4;

        if (pos + ul > data.size()) return false;
        username.assign(reinterpret_cast<const char*>(data.data() + pos), ul);
        pos += ul;

        // Parse GPO results (may be absent in older format)
        gpo_results.clear();
        if (pos + 4 <= data.size()) {
            uint32_t gc = 0;
            std::memcpy(&gc, data.data() + pos, 4);
            pos += 4;

            for (uint32_t i = 0; i < gc; i++) {
                if (pos + 17 > data.size()) return false;
                uuid::uuid gpoid;
                std::memcpy(gpoid.value, data.data() + pos, 16);
                pos += 16;
                bool gpores = (data[pos] != 0);
                pos += 1;
                gpo_results.emplace_back(gpoid, gpores);
            }
        }

        return true;
    }

    // --- Deterministic session group_id (cached) ---

    static std::mutex g_sgidCacheMtx;
    static std::unordered_map<uint64_t, uint64_t> g_sgidCache;

    static uint64_t hashUuids(const uint8_t *data, size_t len) {
        auto hash = netplus::sha256_hash(std::vector<uint8_t>(data, data + len));
        uint64_t id = 0;
        for (int i = 0; i < 8; i++)
            id = (id << 8) | hash[i];
        return id;
    }

    static uint64_t simpleCacheKey(const uint8_t *a, size_t alen, const uint8_t *b, size_t blen) {
        uint64_t k = 0xcbf29ce484222325ULL;
        for (size_t i = 0; i < alen; ++i) k = (k ^ a[i]) * 0x100000001b3ULL;
        for (size_t i = 0; i < blen; ++i) k = (k ^ b[i]) * 0x100000001b3ULL;
        return k;
    }

    uint64_t sessionGroupId(const uuid::uuid &uid, const uuid::uuid &did) {
        uint64_t ckey = simpleCacheKey(uid.value, 16, did.value, 16);
        {
            std::lock_guard<std::mutex> g(g_sgidCacheMtx);
            auto it = g_sgidCache.find(ckey);
            if (it != g_sgidCache.end())
                return it->second;
        }
        uint8_t input[32];
        std::memcpy(input, uid.value, 16);
        std::memcpy(input + 16, did.value, 16);
        uint64_t id = hashUuids(input, 32);
        {
            std::lock_guard<std::mutex> g(g_sgidCacheMtx);
            g_sgidCache[ckey] = id;
        }
        return id;
    }

    uint64_t sidGroupId(const uuid::uuid &sid) {
        static const uint8_t prefix = 0xFF;
        uint64_t ckey = simpleCacheKey(&prefix, 1, sid.value, 16);
        {
            std::lock_guard<std::mutex> g(g_sgidCacheMtx);
            auto it = g_sgidCache.find(ckey);
            if (it != g_sgidCache.end())
                return it->second;
        }
        uint8_t input[17];
        input[0] = 0xFF;
        std::memcpy(input + 1, sid.value, 16);
        uint64_t id = hashUuids(input, 17);
        {
            std::lock_guard<std::mutex> g(g_sgidCacheMtx);
            g_sgidCache[ckey] = id;
        }
        return id;
    }

    // --- session_intercepting_store ---

    session_intercepting_store::session_intercepting_store(
            std::shared_ptr<paritypp::block_store> inner,
            std::shared_ptr<paritypp::memory_block_store> session_store)
        : inner_(std::move(inner)), session_store_(std::move(session_store)) {}

    bool session_intercepting_store::store(uint64_t group_id, uint32_t block_index,
                                            const uint8_t* data, size_t len) {
        return inner_->store(group_id, block_index, data, len);
    }

    bool session_intercepting_store::fetch(uint64_t group_id, uint32_t block_index,
                                            std::vector<uint8_t>& out) {
        return inner_->fetch(group_id, block_index, out);
    }

    bool session_intercepting_store::remove_group(uint64_t group_id) {
        return inner_->remove_group(group_id);
    }

    std::vector<uint32_t> session_intercepting_store::list_blocks(uint64_t group_id) {
        return inner_->list_blocks(group_id);
    }

    uint32_t session_intercepting_store::total_blocks() { return inner_->total_blocks(); }
    uint32_t session_intercepting_store::total_groups() { return inner_->total_groups(); }
    std::vector<uint64_t> session_intercepting_store::list_groups() { return inner_->list_groups(); }

    // --- Cluster ---

    Cluster::Cluster() {}

    Cluster::~Cluster() {
        stop();
    }

    void Cluster::init(const ClusterConfig &cfg) {
        cfg_ = cfg;

        // Setup local block store — file-backed if path configured, otherwise memory
        if (!cfg_.store_path.empty()) {
            std::cerr << "Cluster: using file_block_store at " << cfg_.store_path << std::endl;
            store_ = std::make_shared<paritypp::file_block_store>(cfg_.store_path);
        } else {
            std::cerr << "Cluster: using memory_block_store (no PATH configured)" << std::endl;
            store_ = std::make_shared<paritypp::memory_block_store>();
        }

        // Dedicated memory-only store for sessions
        session_store_ = std::make_shared<paritypp::memory_block_store>();

        // Setup auth
        auth_ = std::make_shared<paritypp::auth_store>();
        // Add self as client (peers use same credentials)
        if (!cfg_.client_name.empty()) {
            auth_->add_client(cfg_.client_name, cfg_.client_key);
        }

        // Setup server config
        paritypp::server::config scfg;
        scfg.bind_address = cfg_.bind_address;
        scfg.port = cfg_.port;
        scfg.cert_file = cfg_.cert_file;
        scfg.key_file = cfg_.key_file;
        scfg.max_connections = 64;
        scfg.require_auth = !cfg_.client_name.empty();

        // Wrap the store with session interceptor so incoming replicated sessions
        // are automatically mirrored into the dedicated session_store_
        auto intercepted = std::make_shared<session_intercepting_store>(store_, session_store_);
        server_ = std::make_unique<paritypp::server>(scfg, intercepted, auth_);

        // Setup client for talking to peers — filter out self
        size_t peer_count = 0;
        if (!cfg_.peers.empty()) {
            // Collect all local identities: hostname + all local IP addresses
            std::vector<std::string> local_addrs;
            {
                char hostname[256] = {};
                gethostname(hostname, sizeof(hostname));
                local_addrs.emplace_back(hostname);
            }
#ifndef _WIN32
            {
                struct ifaddrs *ifaddr = nullptr;
                if (getifaddrs(&ifaddr) == 0) {
                    for (struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
                        if (!ifa->ifa_addr) continue;
                        char buf[INET6_ADDRSTRLEN] = {};
                        if (ifa->ifa_addr->sa_family == AF_INET) {
                            inet_ntop(AF_INET,
                                      &reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr,
                                      buf, sizeof(buf));
                            local_addrs.emplace_back(buf);
                        } else if (ifa->ifa_addr->sa_family == AF_INET6) {
                            inet_ntop(AF_INET6,
                                      &reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr)->sin6_addr,
                                      buf, sizeof(buf));
                            local_addrs.emplace_back(buf);
                        }
                    }
                    freeifaddrs(ifaddr);
                }
            }
#endif

            std::vector<paritypp::client::node_info> nodes;
            size_t self_idx = SIZE_MAX;
            for (size_t pi = 0; pi < cfg_.peers.size(); ++pi) {
                const auto &peer = cfg_.peers[pi];
                bool is_self = false;
                if (peer.port == cfg_.port) {
                    for (const auto &addr : local_addrs) {
                        if (peer.address == addr) {
                            is_self = true;
                            break;
                        }
                    }
                }
                if (is_self) {
                    self_idx = nodes.size();
                    std::cerr << "Cluster: self node index " << pi << " (" << peer.address
                              << ":" << peer.port << ")" << std::endl;
                }
                nodes.push_back({peer.address, peer.port});
            }

            peer_count = nodes.size();

            paritypp::client::credentials pcreds;
            if (!cfg_.client_name.empty()) {
                pcreds.client_name = cfg_.client_name;
                pcreds.key.assign(cfg_.client_key.begin(), cfg_.client_key.end());
            }

            if (!nodes.empty()) {
                pclient_ = std::make_unique<paritypp::client>(
                    cfg_.data_blocks, cfg_.parity_blocks, nodes, pcreds);
                if (self_idx != SIZE_MAX) {
                    pclient_->set_local_node(self_idx, store_);
                }
            } else {
                std::cerr << "Cluster: no peers configured" << std::endl;
            }
        }

        // Register peer status callback so GET_STATUS reports peer counts.
        // This callback runs on the server thread when a remote node queries us,
        // so it must NOT acquire client_mutex_ or do network I/O (deadlock risk).
        // peer_count is based on actual filtered node list (after self-exclusion).
        server_->set_peer_status_callback([peer_count]() -> std::pair<uint32_t, uint32_t> {
            return {static_cast<uint32_t>(peer_count), static_cast<uint32_t>(peer_count)};
        });
    }

    void Cluster::start() {
        if (running_) return;
        running_ = true;
        server_thread_ = std::thread(&Cluster::server_loop, this);
        health_monitor_thread_ = std::thread(&Cluster::health_monitor_loop, this);
    }

    size_t Cluster::waitForPeers(int timeout_s) {
        if (!pclient_) return 0;
        size_t k = cfg_.data_blocks;
        size_t n = cfg_.data_blocks + cfg_.parity_blocks;
        size_t best_online = 0;
        for (int elapsed = 0; elapsed < timeout_s; ++elapsed) {
            try {
                auto &cmtx = getClientMutex();
                std::unique_lock<std::timed_mutex> lock(cmtx, std::chrono::seconds(2));
                if (lock.owns_lock()) {
                    auto health = pclient_->get_cluster_status();
                    if (health.nodes_online > best_online)
                        best_online = health.nodes_online;
                    std::cerr << "Cluster: " << health.nodes_online << "/"
                              << health.nodes_total << " nodes online ("
                              << elapsed << "s/" << timeout_s << "s)" << std::endl;
                    if (health.nodes_online >= n) {
                        std::cerr << "Cluster: all nodes online" << std::endl;
                        return health.nodes_online;
                    }
                    if (health.nodes_online >= k) {
                        std::cerr << "Cluster: DEGRADED — " << health.nodes_online
                                  << "/" << n << " nodes online, continuing" << std::endl;
                        return health.nodes_online;
                    }
                }
            } catch (const std::exception &e) {
                std::cerr << "Cluster: peer probe failed: " << e.what() << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        std::cerr << "Cluster: FAILED — only " << best_online << "/" << n
                  << " nodes reachable after " << timeout_s << "s (need " << k
                  << ")" << std::endl;
        return best_online;
    }

    void Cluster::stop() {
        if (!running_) return;
        running_ = false;
        monitor_cv_.notify_all();
        if (server_) server_->stop();
        if (health_monitor_thread_.joinable()) {
            health_monitor_thread_.join();
        }
        if (server_thread_.joinable()) {
            server_thread_.join();
        }
    }

    void Cluster::server_loop() {
        try {
            server_->run();
        } catch (const netplus::NetException &e) {
            std::cerr << "Cluster server error: " << e.what() << std::endl;
        } catch (const std::exception &e) {
            std::cerr << "Cluster server error: " << e.what() << std::endl;
        }
        running_ = false;
    }

    void Cluster::health_monitor_loop() {
        size_t k = cfg_.data_blocks;
        size_t n = cfg_.data_blocks + cfg_.parity_blocks;
        while (running_) {
            {
                std::unique_lock<std::mutex> lk(monitor_mutex_);
                monitor_cv_.wait_for(lk, std::chrono::seconds(10),
                                     [this]{ return !running_.load(); });
            }
            if (!running_ || !pclient_) continue;
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(2));
                if (!lock.owns_lock()) continue;
                auto health = pclient_->get_cluster_status();
                if (health.nodes_online >= n) {
                    if (degraded_) {
                        std::cerr << "Cluster: recovered — all "
                                  << health.nodes_online << "/" << n
                                  << " nodes online" << std::endl;
                        degraded_ = false;
                    }
                } else if (health.nodes_online >= k) {
                    if (!degraded_) {
                        std::cerr << "Cluster: DEGRADED — " << health.nodes_online
                                  << "/" << n << " nodes online" << std::endl;
                    }
                    degraded_ = true;
                } else {
                    std::cerr << "Cluster: CRITICAL — only " << health.nodes_online
                              << "/" << n << " nodes online (need "
                              << k << ")" << std::endl;
                    degraded_ = true;
                }
            } catch (const std::exception &e) {
                std::cerr << "Cluster: health probe failed: " << e.what() << std::endl;
                degraded_ = true;
            }
        }
    }

    void Cluster::pushSession(const SessionData &sess) {
        if (!pclient_) return;

        auto data = SessionBlock::serialize(sess);

        uuid::uuid uid, did, sid;
        sess.getSid(sid);
        sess.getUid(uid);
        sess.getDid(did);

        uint64_t sgid = sessionGroupId(uid, did);
        uint64_t sid_gid = sidGroupId(sid);

        try {
            std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
            if (!lock.owns_lock()) {
                std::cerr << "Cluster push session: lock timeout" << std::endl;
                return;
            }
            pclient_->store(sgid, data.data(), data.size());
            pclient_->store(sid_gid, data.data(), data.size());
        } catch (const std::exception &e) {
            std::cerr << "Cluster push session: " << e.what() << std::endl;
        }
    }

    bool Cluster::fetchSession(const uuid::uuid &uid, const uuid::uuid &did,
                                uuid::uuid &sid, std::vector<uuid::uuid> &members,
                                std::string &username,
                                std::vector<std::pair<uuid::uuid, bool>> &gpo_results) {
        uint64_t sgid = sessionGroupId(uid, did);

        // Retrieve from peer nodes via erasure coding
        std::vector<uint8_t> data;
        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(2)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                data = pclient_->retrieve(sgid);
                if (!data.empty()) {
                    uuid::uuid d_uid, d_did;
                    if (SessionBlock::deserialize(data, sid, d_uid, d_did, members, username, gpo_results)) {
                        return true;
                    }
                }
            } catch (...) {
                return false;
            }
        }

        return false;
    }

    void Cluster::removeSession(const uuid::uuid &uid, const uuid::uuid &did) {
        uint64_t sgid = sessionGroupId(uid, did);

        // Fetch session to get the SID for removing the SID-keyed entry
        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                std::vector<uint8_t> data = pclient_->retrieve(sgid);
                if (!data.empty()) {
                    uuid::uuid sid, d_uid, d_did;
                    std::vector<uuid::uuid> members;
                    std::string username;
                    std::vector<std::pair<uuid::uuid, bool>> gpo_results;
                    if (SessionBlock::deserialize(data, sid, d_uid, d_did, members, username, gpo_results)) {
                        uint64_t sid_gid = sidGroupId(sid);
                        pclient_->remove(sid_gid);
                    }
                }
                pclient_->remove(sgid);
            } catch (...) {}
        }
    }

    void Cluster::removeSessionBySid(const uuid::uuid &session_id) {
        uint64_t sid_gid = sidGroupId(session_id);

        // Fetch session data to get uid+did key
        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                std::vector<uint8_t> data = pclient_->retrieve(sid_gid);
                if (!data.empty()) {
                    uuid::uuid sid, uid, did;
                    std::vector<uuid::uuid> members;
                    std::string username;
                    std::vector<std::pair<uuid::uuid, bool>> gpo_results;
                    if (SessionBlock::deserialize(data, sid, uid, did, members, username, gpo_results)) {
                        uint64_t sgid = sessionGroupId(uid, did);
                        pclient_->remove(sgid);
                    }
                }
                pclient_->remove(sid_gid);
            } catch (...) {}
        }
    }

    bool Cluster::hasSession(const uuid::uuid &uid, const uuid::uuid &did) {
        uint64_t sgid = sessionGroupId(uid, did);

        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                auto data = pclient_->retrieve(sgid);
                if (!data.empty()) return true;
            } catch (...) {}
        }

        return false;
    }

    bool Cluster::fetchSessionLocalBySid(const uuid::uuid &session_id,
                                          uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                                          std::vector<uuid::uuid> &members,
                                          std::string &username,
                                          std::vector<std::pair<uuid::uuid, bool>> &gpo_results) {
        // No local store — retrieve via erasure coding
        uint64_t sid_gid = sidGroupId(session_id);
        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                auto data = pclient_->retrieve(sid_gid);
                if (!data.empty()) {
                    return SessionBlock::deserialize(data, sid, uid, did, members, username, gpo_results);
                }
            } catch (...) {}
        }
        return false;
    }

    bool Cluster::hasSessionBySid(const uuid::uuid &session_id) {
        uint64_t sid_gid = sidGroupId(session_id);

        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                auto data = pclient_->retrieve(sid_gid);
                if (!data.empty()) return true;
            } catch (...) {}
        }

        return false;
    }

    bool Cluster::fetchSessionBySid(const uuid::uuid &session_id,
                                     uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                                     std::vector<uuid::uuid> &members,
                                     std::string &username,
                                     std::vector<std::pair<uuid::uuid, bool>> &gpo_results) {
        uint64_t sid_gid = sidGroupId(session_id);

        if (pclient_) {
            try {
                std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
                auto data = pclient_->retrieve(sid_gid);
                if (!data.empty()) {
                    if (SessionBlock::deserialize(data, sid, uid, did, members, username, gpo_results)) {
                        return true;
                    }
                }
            } catch (...) {
                return false;
            }
        }

        return false;
    }

    void Cluster::listAllSessions(std::vector<SessionInfo> &out) {
        out.clear();
        if (!pclient_) return;
        std::set<std::string> seen_sids;

        // Collect all group IDs across all nodes
        std::set<uint64_t> all_groups;
        try {
            std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
            if (!lock.owns_lock()) return;
            size_t n = pclient_->get_cluster_status().nodes_total;
            for (size_t i = 0; i < n; i++) {
                std::vector<uint64_t> node_groups;
                if (pclient_->list_groups_on_node(i, node_groups)) {
                    all_groups.insert(node_groups.begin(), node_groups.end());
                }
            }

            // Try to retrieve and deserialize each group as a session
            for (uint64_t gid : all_groups) {
                try {
                    auto data = pclient_->retrieve(gid);
                    if (data.empty()) continue;
                    SessionInfo info;
                    if (SessionBlock::deserialize(data, info.sid, info.uid, info.did,
                                                  info.members, info.username, info.gpo_results)) {
                        std::string key = info.sid.c_str();
                        if (!info.sid.empty() && seen_sids.find(key) == seen_sids.end()) {
                            seen_sids.insert(key);
                            out.push_back(std::move(info));
                        }
                    }
                } catch (...) {}
            }
        } catch (...) {}
    }

    // --- DataOpBlock serialization ---

    std::vector<uint8_t> DataOpBlock::serialize(OpType op, const std::string &domain,
                                                 const uuid::uuid &uid, int dataType,
                                                 const AuthData *records) {
        std::vector<uint8_t> buf;

        // Op type
        buf.push_back(static_cast<uint8_t>(op));

        // Domain
        uint32_t dlen = static_cast<uint32_t>(domain.size());
        buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&dlen), reinterpret_cast<const uint8_t*>(&dlen) + 4);
        buf.insert(buf.end(), domain.begin(), domain.end());

        // UID
        buf.insert(buf.end(), uid.value, uid.value + 16);

        // Data type
        uint32_t dt = static_cast<uint32_t>(dataType);
        buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&dt), reinterpret_cast<const uint8_t*>(&dt) + 4);

        // Records (only for Create/Edit)
        if (op == OpType::Delete || !records) {
            uint32_t rc = 0;
            buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&rc), reinterpret_cast<const uint8_t*>(&rc) + 4);
        } else {
            // Count records
            uint32_t rc = 0;
            for (const AuthData *cur = records; cur; cur = cur->next()) rc++;

            buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&rc), reinterpret_cast<const uint8_t*>(&rc) + 4);

            for (const AuthData *cur = records; cur; cur = cur->next()) {
                // fieldname (255 bytes fixed)
                buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(cur->Data.fieldname),
                           reinterpret_cast<const uint8_t*>(cur->Data.fieldname) + 255);
                // storage type
                uint32_t st = static_cast<uint32_t>(cur->Data.storage);
                buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&st), reinterpret_cast<const uint8_t*>(&st) + 4);
                // data size
                uint32_t ds = static_cast<uint32_t>(cur->Data.datasize);
                buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(&ds), reinterpret_cast<const uint8_t*>(&ds) + 4);
                // data
                if (ds > 0 && cur->Data.data) {
                    buf.insert(buf.end(), reinterpret_cast<const uint8_t*>(cur->Data.data),
                               reinterpret_cast<const uint8_t*>(cur->Data.data) + ds);
                }
            }
        }

        return buf;
    }

    bool DataOpBlock::deserialize(const std::vector<uint8_t> &data,
                                   OpType &op, std::string &domain,
                                   uuid::uuid &uid, int &dataType,
                                   std::vector<AuthData::Record> &records) {
        if (data.size() < 1 + 4) return false;
        size_t pos = 0;

        // Op type
        op = static_cast<OpType>(data[pos++]);

        // Domain
        uint32_t dlen = 0;
        std::memcpy(&dlen, data.data() + pos, 4); pos += 4;
        if (pos + dlen > data.size()) return false;
        domain.assign(reinterpret_cast<const char*>(data.data() + pos), dlen);
        pos += dlen;

        // UID
        if (pos + 16 > data.size()) return false;
        std::memcpy(uid.value, data.data() + pos, 16); pos += 16;

        // Data type
        if (pos + 4 > data.size()) return false;
        uint32_t dt = 0;
        std::memcpy(&dt, data.data() + pos, 4); pos += 4;
        dataType = static_cast<int>(dt);

        // Record count
        if (pos + 4 > data.size()) return false;
        uint32_t rc = 0;
        std::memcpy(&rc, data.data() + pos, 4); pos += 4;

        records.clear();
        for (uint32_t i = 0; i < rc; i++) {
            if (pos + 255 + 4 + 4 > data.size()) return false;
            AuthData::Record rec;
            std::memcpy(rec.fieldname, data.data() + pos, 255); pos += 255;

            uint32_t st = 0;
            std::memcpy(&st, data.data() + pos, 4); pos += 4;
            rec.storage = static_cast<int>(st);

            uint32_t ds = 0;
            std::memcpy(&ds, data.data() + pos, 4); pos += 4;
            rec.datasize = ds;

            if (ds > 0) {
                if (pos + ds > data.size()) return false;
                rec.data = new char[ds];
                std::memcpy(rec.data, data.data() + pos, ds);
                pos += ds;
            }

            std::memcpy(rec.ruid, uid.value, 16);
            rec.type = dataType;
            records.push_back(rec);
        }

        return true;
    }

    // --- Data replication ---

    void Cluster::replicateOp(OpType op, const std::string &domain,
                               const uuid::uuid &uid, int dataType,
                               const AuthData *records) {
        if (!pclient_) return;

        auto data = DataOpBlock::serialize(op, domain, uid, dataType, records);

        auto hash = netplus::sha256_hash(data);
        uint64_t localId = 0;
        for (int i = 0; i < 8; i++) localId = (localId << 8) | hash[i];

        // Distribute erasure-coded shards to peer nodes
        try {
            std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5)); if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
            pclient_->store(localId, data.data(), data.size());
        } catch (const netplus::NetException &e) {
            std::cerr << "Cluster replicate failed: " << e.what() << std::endl;
        } catch (const std::exception &e) {
            std::cerr << "Cluster replicate failed: " << e.what() << std::endl;
        }
    }



};
