/*******************************************************************************
 * 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 <cstring>
#include <iostream>
#include <algorithm>
#include <thread>
#include <chrono>
#include <future>
#include <map>
#include <tuple>
#include <shared_mutex>

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

#include "cluster.h"
#include "../cluster.h"

#ifndef NDEBUG
#define DBG_LOG(x) do { std::cerr << x; } while(0)
#else
#define DBG_LOG(x) do {} while(0)
#endif

namespace authdb {

    // Static members
    std::shared_mutex ClusterBackend::s_cache_mutex_;
    std::map<std::string, std::shared_ptr<DomainCache>> ClusterBackend::s_domain_cache_;

    std::shared_ptr<DomainCache> ClusterBackend::getDomainCache() {
        {
            std::shared_lock<std::shared_mutex> rl(s_cache_mutex_);
            auto it = s_domain_cache_.find(_Domain);
            if (it != s_domain_cache_.end())
                return it->second;
        }
        std::unique_lock<std::shared_mutex> wl(s_cache_mutex_);
        auto &ptr = s_domain_cache_[_Domain];
        if (!ptr)
            ptr = std::make_shared<DomainCache>();
        return ptr;
    }

    uint64_t ClusterBackend::domainGroupId() const {
        std::vector<uint8_t> input(_Domain.begin(), _Domain.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;
    }

    // Helper: extract revision from a backend buffer
    static size_t bufferRevision(const std::vector<uint8_t> &buf) {
        if (buf.size() < sizeof(AuthHeader))
            return 0;
        AuthHeader head;
        std::memcpy(&head, buf.data(), sizeof(AuthHeader));
        return head.Revesion;
    }

    // Validate that all records in buffer have consistent offsets.
    static bool bufferValid(const std::vector<uint8_t> &buf) {
        if (buf.size() < sizeof(AuthHeader))
            return buf.empty();

        size_t rd = sizeof(AuthHeader);
        while (rd + sizeof(AuthData::Record) <= buf.size()) {
            AuthData::Record rec;
            std::memcpy(&rec, buf.data() + rd, sizeof(AuthData::Record));
            rd += sizeof(AuthData::Record);
            if (rd + rec.datasize > buf.size())
                return false;
            rd += rec.datasize;
        }
        return true;
    }

    void ClusterBackend::forceRefresh() {
        auto cache = getDomainCache();
        cache->last_fetch = {};
    }

    std::set<uint64_t> ClusterBackend::knownDomainGroupIds() {
        std::shared_lock<std::shared_mutex> rl(s_cache_mutex_);
        std::set<uint64_t> ids;
        for (auto &[domain, _] : s_domain_cache_) {
            std::vector<uint8_t> input(domain.begin(), domain.end());
            auto hash = netplus::sha256_hash(input);
            uint64_t id = 0;
            for (int i = 0; i < 8; i++)
                id = (id << 8) | hash[i];
            ids.insert(id);
        }
        return ids;
    }

    static uint64_t fnv1a_hash(const std::vector<uint8_t> &data) {
        uint64_t hash = 14695981039346656037ULL;
        for (auto b : data) {
            hash ^= b;
            hash *= 1099511628211ULL;
        }
        return hash;
    }

    static constexpr uint32_t MANIFEST_MAGIC = 0x454E5459; // "ENTY"

    uint64_t ClusterBackend::entityGroupId(const EntityKey &ek) const {
        std::vector<uint8_t> input(_Domain.begin(), _Domain.end());
        input.insert(input.end(), ek.ruid, ek.ruid + 16);
        int32_t t = ek.type;
        input.insert(input.end(),
                     reinterpret_cast<uint8_t*>(&t),
                     reinterpret_cast<uint8_t*>(&t) + sizeof(int32_t));
        auto hash = netplus::sha256_hash(input);
        uint64_t id = 0;
        for (int i = 0; i < 8; i++)
            id = (id << 8) | hash[i];
        return id;
    }

    std::map<ClusterBackend::EntityKey, std::vector<uint8_t>>
    ClusterBackend::parseEntities() const {
        std::map<EntityKey, std::vector<uint8_t>> result;

        size_t rd = sizeof(AuthHeader);
        while (rd + sizeof(AuthData::Record) <= _Buffer.size()) {
            AuthData::Record rec;
            std::memcpy(&rec, _Buffer.data() + rd, sizeof(AuthData::Record));
            size_t record_start = rd;
            rd += sizeof(AuthData::Record);

            if (rec.type == DataType::EmptyData) {
                rd += rec.datasize;
                continue;
            }

            if (rd + rec.datasize > _Buffer.size())
                break;

            EntityKey ek;
            std::memcpy(ek.ruid, rec.ruid, 16);
            ek.type = rec.type;

            size_t total = sizeof(AuthData::Record) + rec.datasize;
            auto &entity_buf = result[ek];
            size_t old_size = entity_buf.size();
            entity_buf.resize(old_size + total);
            std::memcpy(entity_buf.data() + old_size, _Buffer.data() + record_start, total);

            rd += rec.datasize;
        }

        return result;
    }

    void ClusterBackend::pushManifest(const std::set<EntityKey> &keys) {
        AuthHeader head;
        if (_Buffer.size() >= sizeof(AuthHeader))
            std::memcpy(&head, _Buffer.data(), sizeof(AuthHeader));

        uint32_t magic = MANIFEST_MAGIC;
        uint32_t count = static_cast<uint32_t>(keys.size());

        std::vector<uint8_t> manifest;
        manifest.resize(sizeof(AuthHeader) + sizeof(uint32_t) + sizeof(uint32_t));
        size_t off = 0;
        std::memcpy(manifest.data() + off, &head, sizeof(AuthHeader)); off += sizeof(AuthHeader);
        std::memcpy(manifest.data() + off, &magic, sizeof(uint32_t));  off += sizeof(uint32_t);
        std::memcpy(manifest.data() + off, &count, sizeof(uint32_t));  off += sizeof(uint32_t);

        for (auto &ek : keys) {
            size_t pos = manifest.size();
            manifest.resize(pos + 16 + sizeof(int32_t));
            std::memcpy(manifest.data() + pos, ek.ruid, 16);
            int32_t t = ek.type;
            std::memcpy(manifest.data() + pos + 16, &t, sizeof(int32_t));
        }

        // Append full record data so fetch only needs 1 retrieve call
        if (_Buffer.size() > sizeof(AuthHeader))
            manifest.insert(manifest.end(),
                            _Buffer.begin() + sizeof(AuthHeader),
                            _Buffer.end());

        g_Cluster->queueDataPush(domainGroupId(), std::move(manifest));
    }

    void ClusterBackend::pushManifestSync(const std::set<EntityKey> &keys) {
        if (!g_Cluster || !g_Cluster->isRunning()) return;
        auto &client = g_Cluster->getClient();
        if (!client) return;

        AuthHeader head;
        if (_Buffer.size() >= sizeof(AuthHeader))
            std::memcpy(&head, _Buffer.data(), sizeof(AuthHeader));

        uint32_t magic = MANIFEST_MAGIC;
        uint32_t count = static_cast<uint32_t>(keys.size());

        std::vector<uint8_t> manifest;
        manifest.resize(sizeof(AuthHeader) + sizeof(uint32_t) + sizeof(uint32_t));
        size_t off = 0;
        std::memcpy(manifest.data() + off, &head, sizeof(AuthHeader)); off += sizeof(AuthHeader);
        std::memcpy(manifest.data() + off, &magic, sizeof(uint32_t));  off += sizeof(uint32_t);
        std::memcpy(manifest.data() + off, &count, sizeof(uint32_t));  off += sizeof(uint32_t);

        for (auto &ek : keys) {
            size_t pos = manifest.size();
            manifest.resize(pos + 16 + sizeof(int32_t));
            std::memcpy(manifest.data() + pos, ek.ruid, 16);
            int32_t t = ek.type;
            std::memcpy(manifest.data() + pos + 16, &t, sizeof(int32_t));
        }

        // Append full record data
        if (_Buffer.size() > sizeof(AuthHeader))
            manifest.insert(manifest.end(),
                            _Buffer.begin() + sizeof(AuthHeader),
                            _Buffer.end());

        try {
            client->store(domainGroupId(), manifest.data(), manifest.size());
        } catch (const std::exception &e) {
            std::cerr << "ClusterBackend::pushManifestSync failed: " << e.what() << std::endl;
        }
    }

    void ClusterBackend::fetchFromCluster() {
        if (!g_Cluster || !g_Cluster->isRunning())
            return;

        auto cache = getDomainCache();
        auto now = std::chrono::steady_clock::now();

        // Fast path: check if cached data is fresh
        bool do_peer_fetch = (now - cache->last_fetch) >= std::chrono::seconds(10);

        uint64_t cur_epoch = g_Cluster->getRecoveryEpoch();
        if (cur_epoch != cache->recovery_epoch)
            do_peer_fetch = true;

        if (!do_peer_fetch) {
            if (_Buffer.size() <= sizeof(AuthHeader) && !cache->buffer.empty()) {
                _Buffer = cache->buffer;
                _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
                DBG_LOG("[CLUSTER-BE] fetchFromCluster domain=" << _Domain
                          << " using cached buf_size=" << _Buffer.size() << "\n");
            }
            return;
        }

        // Serialize fetches per domain — don't block if another thread is fetching
        std::unique_lock<std::mutex> flk(cache->fetch_mutex, std::try_to_lock);
        if (!flk.owns_lock()) {
            // Another thread is already fetching — use cached data, don't wait
            if (!cache->buffer.empty()) {
                _Buffer = cache->buffer;
                _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
            }
            return;
        }

        uint64_t dgid = domainGroupId();
        DBG_LOG("[CLUSTER-BE] fetchFromCluster domain=" << _Domain
                  << " buf_size=" << _Buffer.size() << " dirty=" << _Dirty.load() << "\n");

        auto &client = g_Cluster->getReadClient();
        if (!client) return;

        // Single retrieve with timeout: manifest contains header + magic + entity keys + full record data
        std::vector<uint8_t> manifest;

        // Check if a previous async fetch completed in the background
        if (cache->pending_retrieve.valid()) {
            auto st = cache->pending_retrieve.wait_for(std::chrono::milliseconds(0));
            if (st == std::future_status::ready) {
                try {
                    manifest = cache->pending_retrieve.get();
                    cache->last_fetch = now;
                    cache->recovery_epoch = cur_epoch;
                } catch (const std::exception &e) {
                    std::cerr << "ClusterBackend::fetchFromCluster: background retrieve failed: "
                              << e.what() << std::endl;
                    cache->last_fetch = now; // prevent immediate retry
                    _ClusterDataExists.store(true);
                    return;
                } catch (...) {
                    cache->last_fetch = now;
                    _ClusterDataExists.store(true);
                    return;
                }
            } else {
                // Previous fetch still running — use cached data, don't start another
                if (!cache->buffer.empty()) {
                    _Buffer = cache->buffer;
                    _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
                }
                return;
            }
        } else {
            // No pending fetch — launch async retrieve.
            // If the cache is empty (first fetch after startup), wait synchronously
            // with a timeout so the server doesn't show "waiting for peers" on every
            // first request.  Subsequent fetches are fire-and-forget.
            bool need_initial_data = cache->buffer.empty() &&
                                     _Buffer.size() <= sizeof(AuthHeader);
            try {
                auto *raw_client = client.get();
                cache->pending_retrieve = std::async(std::launch::async,
                    [raw_client, dgid]() {
                        return raw_client->retrieve(dgid);
                    });

                if (need_initial_data) {
                    // First fetch: wait up to 5s so we have data for the first request
                    static constexpr auto INITIAL_TIMEOUT = std::chrono::seconds(5);
                    if (cache->pending_retrieve.wait_for(INITIAL_TIMEOUT) == std::future_status::ready) {
                        manifest = cache->pending_retrieve.get();
                        cache->last_fetch = now;
                        cache->recovery_epoch = cur_epoch;
                    } else {
                        std::cerr << "[CLUSTER-BE] domain=" << _Domain
                                  << " initial retrieve timed out after 5s" << std::endl;
                        return;
                    }
                }
            } catch (const std::exception &e) {
                std::cerr << "ClusterBackend::fetchFromCluster: async launch failed: "
                          << e.what() << std::endl;
            }

            if (!need_initial_data) {
                // Fire-and-forget: return cached data immediately.
                // Update last_fetch so we don't re-launch for at least 10s.
                cache->last_fetch = now;
                if (!cache->buffer.empty()) {
                    _Buffer = cache->buffer;
                    _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));
                }
                return;
            }
        }

        if (manifest.size() < sizeof(AuthHeader) + sizeof(uint32_t) + sizeof(uint32_t)) {
            if (!manifest.empty())
                _ClusterDataExists.store(true);
            return;
        }

        // Parse header
        AuthHeader head;
        std::memcpy(&head, manifest.data(), sizeof(AuthHeader));

        // Revision guard: never replace local cache with older data.
        // This prevents data loss when a recovering node returns stale manifests.
        size_t cached_rev = bufferRevision(cache->buffer.empty() ? _Buffer : cache->buffer);
        if (head.Revesion < cached_rev) {
            DBG_LOG("[CLUSTER-BE] domain=" << _Domain
                      << " retrieved rev=" << head.Revesion
                      << " < cached rev=" << cached_rev << ", discarding stale manifest\n");
            return;
        }

        size_t moff = sizeof(AuthHeader);
        uint32_t magic = 0;
        std::memcpy(&magic, manifest.data() + moff, sizeof(uint32_t));
        moff += sizeof(uint32_t);

        if (magic != MANIFEST_MAGIC) {
            _ClusterDataExists.store(true);
            std::cerr << "[CLUSTER-BE] domain=" << _Domain
                      << " manifest has wrong magic (legacy format?), needs re-import" << std::endl;
            return;
        }

        uint32_t entity_count = 0;
        std::memcpy(&entity_count, manifest.data() + moff, sizeof(uint32_t));
        moff += sizeof(uint32_t);

        // Skip entity key list
        size_t key_section = entity_count * (16 + sizeof(int32_t));
        if (moff + key_section > manifest.size()) return;

        // Parse entity keys for hash tracking
        entity_hashes_.clear();
        std::vector<EntityKey> keys;
        keys.reserve(entity_count);
        for (uint32_t i = 0; i < entity_count; i++) {
            EntityKey ek;
            std::memcpy(ek.ruid, manifest.data() + moff, 16);
            moff += 16;
            int32_t t = 0;
            std::memcpy(&t, manifest.data() + moff, sizeof(int32_t));
            moff += sizeof(int32_t);
            ek.type = t;
            keys.push_back(ek);
        }

        // Rebuild _Buffer: header + record data that follows the entity key list
        _Buffer.resize(sizeof(AuthHeader));
        std::memcpy(_Buffer.data(), &head, sizeof(AuthHeader));

        if (moff < manifest.size()) {
            _Buffer.insert(_Buffer.end(),
                           manifest.begin() + moff,
                           manifest.end());
        }

        // Validate buffer integrity
        if (!bufferValid(_Buffer)) {
            std::cerr << "[CLUSTER-BE] domain=" << _Domain
                      << " manifest record data is corrupt, discarding" << std::endl;
            _Buffer.resize(sizeof(AuthHeader));
        }

        _ClusterDataExists.store(_Buffer.size() > sizeof(AuthHeader));

        // Build entity hashes from records
        auto entities = parseEntities();
        for (auto &[ek, data] : entities) {
            entity_hashes_[ek] = fnv1a_hash(data);
        }

        // Update shared cache
        cache->buffer = _Buffer;

        DBG_LOG("[CLUSTER-BE] fetchFromCluster rebuilt buf_size=" << _Buffer.size()
                  << " entities=" << entity_hashes_.size() << "\n");
    }

    void ClusterBackend::pushToCluster() {
        // Sync push for destructor — single manifest contains full buffer
        if (!_Dirty) return;
        newRevesion();

        auto entities = parseEntities();
        std::set<EntityKey> keys;
        for (auto &[ek, data] : entities)
            keys.insert(ek);

        pushManifestSync(keys);
        _Dirty = false;
    }

    ClusterBackend::ClusterBackend(const char *domain, size_t version, const char *filepath)
        : _Domain(domain), _Pos(0), _Dirty(false)
    {
        // Try the shared domain cache first — no network I/O needed.
        // This is critical: the constructor must NOT call pclient_->retrieve()
        // because the push_worker may hold pclient_->mutex_ for seconds,
        // blocking this constructor and causing HTTP timeouts.
        if (g_Cluster && g_Cluster->isRunning()) {
            auto cache = getDomainCache();
            if (!cache->buffer.empty()) {
                _Buffer = cache->buffer;
                _ClusterDataExists.store(true);
            } else {
                // No cached data yet — check local block store (no network)
                uint64_t dgid = domainGroupId();
                auto &store = g_Cluster->getStore();
                auto blocks = store.list_blocks(dgid);
                if (!blocks.empty())
                    _ClusterDataExists.store(true);
            }
        }

        if (_Buffer.empty()) {
            AuthHeader head;
            head.Version = version;
            head.Revesion = 0;
            head.start = 0xFE;
            memset(head.domain, 0, 255);
            snprintf(head.domain, 255, "%s", domain);

            _Buffer.resize(sizeof(AuthHeader));
            std::memcpy(_Buffer.data(), &head, sizeof(AuthHeader));
            _Dirty = false;
        }
    }

    ClusterBackend::~ClusterBackend() {
        if (_Dirty) {
            try {
                pushToCluster();
            } catch (const std::exception &e) {
                std::cerr << "ClusterBackend::~ClusterBackend: pushToCluster failed: "
                          << e.what() << std::endl;
            } catch (...) {
                std::cerr << "ClusterBackend::~ClusterBackend: pushToCluster failed (unknown error)"
                          << std::endl;
            }
        }
    }

    void ClusterBackend::read(unsigned char *src, size_t srcsize) {
        if (_Pos + srcsize > _Buffer.size())
            throw AuthBackendError("cluster backend: read past end of data!");

        std::memcpy(src, _Buffer.data() + _Pos, srcsize);
        _Pos += srcsize;
    }

    void ClusterBackend::write(const unsigned char *dest, size_t destsize) {
        if (_Pos + destsize > _Buffer.size())
            _Buffer.resize(_Pos + destsize);

        std::memcpy(_Buffer.data() + _Pos, dest, destsize);
        _Pos += destsize;
        _Dirty = true;
    }

    void ClusterBackend::prefetch() {
        // fetchFromCluster() handles the 30s throttle and domain-level
        // serialization via the shared DomainCache.
        bool was_dirty = _Dirty.load();
        DBG_LOG("[CLUSTER-BE] prefetch() domain=" << _Domain
                  << " buf_size=" << _Buffer.size() << " dirty=" << was_dirty << "\n");
        fetchFromCluster();
        DBG_LOG("[CLUSTER-BE] prefetch() after fetch buf_size=" << _Buffer.size() << "\n");
        if (!was_dirty)
            _Dirty = false;
    }

    void ClusterBackend::lock() {
        DBG_LOG("[CLUSTER-BE] lock() domain=" << _Domain
                  << " buf_size=" << _Buffer.size() << " dirty=" << _Dirty.load() << "\n");
        // Network fetch already done in prefetch(), nothing blocking here.
    }

    void ClusterBackend::unlock() {
        DBG_LOG("[CLUSTER-BE] unlock() domain=" << _Domain
                  << " buf_size=" << _Buffer.size() << " dirty=" << _Dirty.load() << "\n");
        if (_Dirty) {
            newRevesion();

            if (g_Cluster && g_Cluster->isRunning()) {
                // Check if any entity actually changed via hash comparison
                auto entities = parseEntities();
                std::set<EntityKey> current_keys;
                bool changed = false;

                for (auto &[ek, data] : entities) {
                    current_keys.insert(ek);
                    uint64_t hash = fnv1a_hash(data);
                    auto it = entity_hashes_.find(ek);
                    if (it == entity_hashes_.end()) {
                        entity_hashes_[ek] = hash;
                        changed = true;
                    } else if (it->second != hash) {
                        it->second = hash;
                        changed = true;
                    }
                }

                // Detect deleted entities
                for (auto it = entity_hashes_.begin(); it != entity_hashes_.end(); ) {
                    if (current_keys.find(it->first) == current_keys.end()) {
                        it = entity_hashes_.erase(it);
                        changed = true;
                    } else {
                        ++it;
                    }
                }

                // Single manifest push contains full buffer — no per-entity stores needed
                if (changed)
                    pushManifest(current_keys);

                // Update shared cache so other instances see the latest data
                auto cache = getDomainCache();
                cache->buffer = _Buffer;
            }
            _Dirty = false;
        }
    }

    void ClusterBackend::setPos(size_t pos) {
        _Pos = pos;
    }

    size_t ClusterBackend::getPos() {
        return _Pos;
    }

    size_t ClusterBackend::end() {
        return _Buffer.size();
    }

    size_t ClusterBackend::getRevesion() {
        if (_Buffer.size() < sizeof(AuthHeader))
            return 0;

        size_t cur = _Pos;
        AuthHeader head;
        std::memcpy(&head, _Buffer.data(), sizeof(AuthHeader));
        return head.Revesion;
    }

    void ClusterBackend::newRevesion() {
        if (_Buffer.size() < sizeof(AuthHeader))
            return;

        AuthHeader head;
        std::memcpy(&head, _Buffer.data(), sizeof(AuthHeader));
        ++head.Revesion;
        std::memcpy(_Buffer.data(), &head, sizeof(AuthHeader));
        _Dirty = true;
    }

    void ClusterBackend::purge() {
        if (!g_Cluster || !g_Cluster->isRunning())
            return;

        auto &client = g_Cluster->getClient();
        if (client) {
            try {
                client->remove(domainGroupId());
            } catch (const std::exception &e) {
                std::cerr << "ClusterBackend::purge: remove failed: " << e.what() << std::endl;
            }
        }

        entity_hashes_.clear();
        _Buffer.clear();
        _Dirty = false;
    }

    void ClusterBackend::vacuum() {
        if (_Buffer.size() <= sizeof(AuthHeader))
            return;

        AuthHeader head;
        std::memcpy(&head, _Buffer.data(), sizeof(AuthHeader));

        // Collect valid records, deduplicate keeping last occurrence
        struct RecordEntry {
            AuthData::Record rec;
            std::vector<unsigned char> data;
        };

        std::map<std::tuple<std::string,std::string,int>, RecordEntry> dedup;
        std::vector<std::tuple<std::string,std::string,int>> insertOrder;

        size_t rd = sizeof(AuthHeader);

        while (rd + sizeof(AuthData::Record) <= _Buffer.size()) {
            RecordEntry entry;
            std::memcpy(&entry.rec, _Buffer.data() + rd, sizeof(AuthData::Record));
            size_t datasize = entry.rec.datasize;
            rd += sizeof(AuthData::Record);

            if (entry.rec.type == DataType::EmptyData) {
                rd += datasize;
                continue;
            }

            if (rd + datasize > _Buffer.size())
                break;

            entry.data.resize(datasize);
            if (datasize > 0)
                std::memcpy(entry.data.data(), _Buffer.data() + rd, datasize);
            rd += datasize;

            std::string ruid_str(reinterpret_cast<char*>(entry.rec.ruid), 16);
            std::string fname(entry.rec.fieldname);
            auto key = std::make_tuple(ruid_str, fname, entry.rec.type);

            if (dedup.find(key) == dedup.end())
                insertOrder.push_back(key);
            dedup[key] = std::move(entry);
        }

        // Rebuild buffer
        std::vector<uint8_t> newbuf(sizeof(AuthHeader));
        std::memcpy(newbuf.data(), &head, sizeof(AuthHeader));

        for (const auto &key : insertOrder) {
            auto it = dedup.find(key);
            if (it == dedup.end()) continue;
            const auto &entry = it->second;

            size_t pos = newbuf.size();
            newbuf.resize(pos + sizeof(AuthData::Record) + entry.data.size());
            std::memcpy(newbuf.data() + pos, &entry.rec, sizeof(AuthData::Record));
            if (!entry.data.empty())
                std::memcpy(newbuf.data() + pos + sizeof(AuthData::Record),
                            entry.data.data(), entry.data.size());
        }

        // Only mark dirty if buffer content actually changed
        if (newbuf.size() != _Buffer.size() ||
            std::memcmp(newbuf.data(), _Buffer.data(), newbuf.size()) != 0) {
            _Buffer = std::move(newbuf);
            _Dirty = true;
        }
    }

}
