/*
 * Cluster-mode performance tests for authdb:
 * - 3-node local cluster (k=2, m=1) with memory-only stores
 * - ClusterBackend: write/read/list/remove timing, storage overhead
 * - ClusterSession: add/get/remove latency, memory usage
 * - Data replication round-trip times
 *
 * Generates self-signed TLS certificates at test startup (QUIC requires TLS).
 * Each of the 3 nodes runs on a separate port on 127.0.0.1.
 */
#include <gtest/gtest.h>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <numeric>
#include <string>
#include <thread>
#include <vector>

#ifdef _WIN32
#include <process.h>
#include <io.h>
#include <windows.h>
#include <psapi.h>
#define test_getpid  _getpid
#define test_unlink  _unlink
#else
#include <unistd.h>
#define test_getpid  getpid
#define test_unlink  unlink
#endif

#include <uuidp.h>
#include <netplus/utils/certgen.h>

#include "backend.h"
#include "authdb.h"
#include "user.h"
#include "group.h"
#include "gpo.h"
#include "session.h"
#include "types.h"
#include "exception.h"
#include "cluster.h"
#include "client.h"
#include "index.h"

namespace authdb {
    std::mutex AuthClientLock;
    extern Cluster *g_Cluster;
}

using authdb::AuthBackend;
using authdb::AuthHeader;
using authdb::User;
using authdb::Group;
using authdb::Gpo;
using authdb::Cluster;
using authdb::ClusterConfig;
using authdb::ClusterNode;
using authdb::ClusterSession;
using authdb::SessionData;
using authdb::RecordIndex;

// ═════════════════════════════════════════════════════════════
//  Helpers
// ═════════════════════════════════════════════════════════════

static size_t getRSSKb() {
#ifdef _WIN32
    PROCESS_MEMORY_COUNTERS pmc;
    if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
        return pmc.WorkingSetSize / 1024;
    return 0;
#else
    std::ifstream ifs("/proc/self/status");
    std::string line;
    while (std::getline(ifs, line)) {
        if (line.rfind("VmRSS:", 0) == 0) {
            size_t kb = 0;
            sscanf(line.c_str(), "VmRSS: %zu kB", &kb);
            return kb;
        }
    }
    return 0;
#endif
}

using Clock = std::chrono::high_resolution_clock;

struct TimingStats {
    double min_us;
    double max_us;
    double avg_us;
    double total_ms;
};

static TimingStats computeStats(const std::vector<double> &samples_us) {
    TimingStats s{};
    if (samples_us.empty()) return s;
    s.min_us = *std::min_element(samples_us.begin(), samples_us.end());
    s.max_us = *std::max_element(samples_us.begin(), samples_us.end());
    s.total_ms = std::accumulate(samples_us.begin(), samples_us.end(), 0.0) / 1000.0;
    s.avg_us = (s.total_ms * 1000.0) / static_cast<double>(samples_us.size());
    return s;
}

static void printStats(const char *label, const TimingStats &s, size_t n) {
    std::cout << "  " << label << " (" << n << " ops): "
              << "min=" << s.min_us << " µs  "
              << "avg=" << s.avg_us << " µs  "
              << "max=" << s.max_us << " µs  "
              << "total=" << s.total_ms << " ms\n";
}

// ═════════════════════════════════════════════════════════════
//  TLS certificate generation (self-signed, test-only)
// ═════════════════════════════════════════════════════════════

static std::string g_certFile;
static std::string g_keyFile;

static std::string tempDir() {
#ifdef _WIN32
    const char *tmp = std::getenv("TEMP");
    if (!tmp) tmp = std::getenv("TMP");
    return tmp ? std::string(tmp) + "\\" : "C:\\Temp\\";
#else
    return "/tmp/";
#endif
}

static bool generateTestCerts() {
    std::string prefix = tempDir() + "authdb_cluster_test_" + std::to_string(test_getpid());
    g_certFile = prefix + ".crt";
    g_keyFile  = prefix + ".key.der";

    // Generate 2048-bit RSA self-signed cert using libnetplus certgen API
    netplus::CertGenConfig cfg;
    cfg.commonName = "authdb-test";
    cfg.rsaBits    = 2048;
    cfg.validDays  = 1;

    netplus::CertKeyPair pair;
    if (!netplus::generateSelfSignedCert(cfg, pair)) {
        std::cerr << "Failed to generate test TLS certificates\n";
        return false;
    }

    // Write PEM cert and DER key (paritypp QUIC expects DER private key)
    if (!netplus::writePemFile(g_certFile, pair.certPem)) {
        std::cerr << "Failed to write cert file\n";
        return false;
    }
    if (!netplus::writeDerFile(g_keyFile, pair.keyDer)) {
        std::cerr << "Failed to write key file\n";
        return false;
    }

    return true;
}

static void cleanupTestCerts() {
    if (!g_certFile.empty()) test_unlink(g_certFile.c_str());
    if (!g_keyFile.empty()) test_unlink(g_keyFile.c_str());
}

// ═════════════════════════════════════════════════════════════
//  Fixture: 3-node local cluster with file backend
// ═════════════════════════════════════════════════════════════

static constexpr int NUM_NODES = 3;

class ClusterPerformanceTest : public ::testing::Test {
protected:
    static Cluster *clusters[NUM_NODES];
    static std::string fileDbPath;
    static int basePorts[NUM_NODES];
    static bool clusterReady;

    AuthBackend *fileBackend = nullptr;   // file backend (for populating data)
    AuthBackend *clusterBackend = nullptr; // cluster backend

    static void SetUpTestSuite() {
        if (!generateTestCerts()) {
            GTEST_SKIP() << "Could not generate TLS certificates";
            return;
        }

        // Allocate 3 consecutive ports based on PID
        int basePort = 14433 + (test_getpid() % 10000);
        for (int i = 0; i < NUM_NODES; ++i)
            basePorts[i] = basePort + i;

        fileDbPath = tempDir() + "authdb_cluster_perf_" + std::to_string(test_getpid()) + ".db";

        // Build common peer list (all 3 nodes)
        std::vector<ClusterNode> allPeers;
        for (int i = 0; i < NUM_NODES; ++i) {
            ClusterNode node;
            node.address = "127.0.0.1";
            node.port = basePorts[i];
            allPeers.push_back(node);
        }

        // Init and start each node
        for (int i = 0; i < NUM_NODES; ++i) {
            clusters[i] = new Cluster();

            ClusterConfig cfg;
            cfg.bind_address = "127.0.0.1";
            cfg.port = basePorts[i];
            cfg.cert_file = g_certFile;
            cfg.key_file = g_keyFile;
            cfg.client_name = "test";
            cfg.client_key = "testkey123";
            cfg.store_path = "";  // memory-only
            cfg.data_blocks = 2;
            cfg.parity_blocks = 1;
            cfg.peers = allPeers;

            clusters[i]->init(cfg);
            clusters[i]->start();
        }

        // Use the first node as the active g_Cluster
        authdb::g_Cluster = clusters[0];

        // Wait for cluster to become ready (need k=2 nodes online)
        size_t online = clusters[0]->waitForPeers(10);
        clusterReady = (online >= 2);
        if (!clusterReady) {
            std::cerr << "WARNING: Cluster not ready (" << online
                      << " nodes online, need 2), some tests may be skipped\n";
        } else {
            std::cerr << "Cluster ready: " << online << "/" << NUM_NODES
                      << " nodes online\n";
        }
    }

    static void TearDownTestSuite() {
        authdb::g_Cluster = nullptr;
        for (int i = 0; i < NUM_NODES; ++i) {
            if (clusters[i]) {
                clusters[i]->stop();
                delete clusters[i];
                clusters[i] = nullptr;
            }
        }
        test_unlink(fileDbPath.c_str());
        test_unlink((fileDbPath + ".lock").c_str());
        cleanupTestCerts();
    }

    void SetUp() override {
        if (!clusterReady)
            GTEST_SKIP() << "Cluster not ready";

        // File backend for populating user data (needed by createSessionData)
        fileBackend = new AuthBackend(authdb::File, fileDbPath, "clusterdomain");

        // Cluster backend
        clusterBackend = new AuthBackend(authdb::ClusterStore, "", "clusterdomain");
    }

    void TearDown() override {
        delete clusterBackend;
        delete fileBackend;
    }
};

Cluster *ClusterPerformanceTest::clusters[NUM_NODES] = {};
std::string ClusterPerformanceTest::fileDbPath;
int ClusterPerformanceTest::basePorts[NUM_NODES] = {};
bool ClusterPerformanceTest::clusterReady = false;

// ═════════════════════════════════════════════════════════════
//  1.  ClusterBackend User CRUD benchmark
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, UserCreateReadRemoveBenchmark) {
    constexpr size_t N = 100;

    std::vector<uuid::uuid> uids(N);
    std::vector<double> createTimes, readTimes, listTimes, removeTimes;

    size_t rssBefore = getRSSKb();

    // ── CREATE via cluster backend ──
    User user;
    for (size_t i = 0; i < N; ++i) {
        uids[i].generate();
        class authdb::UserData udat(uids[i]);
        udat.setUserName("cuser_" + std::to_string(i));
        udat.setFirstName("CFirst" + std::to_string(i));
        udat.setLastName("CLast" + std::to_string(i));
        udat.setMail("cuser" + std::to_string(i) + "@cluster.test");
        udat.setPwHash("chash_" + std::to_string(i));

        auto t0 = Clock::now();
        user.create(*clusterBackend, &udat);
        auto t1 = Clock::now();
        createTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    size_t rssAfterCreate = getRSSKb();

    // ── LIST ──
    {
        std::vector<uuid::uuid> listed;
        auto t0 = Clock::now();
        user.list(*clusterBackend, listed);
        auto t1 = Clock::now();
        listTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
        EXPECT_EQ(listed.size(), N);
    }

    // ── READ (info) ──
    for (size_t i = 0; i < N; ++i) {
        class authdb::UserData udat(uids[i]);
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        user.info(*clusterBackend, udat, pos);
        auto t1 = Clock::now();
        readTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

        EXPECT_EQ(udat.getUsername(), "cuser_" + std::to_string(i));
    }

    size_t rssAfterRead = getRSSKb();

    // ── REMOVE ──
    for (size_t i = 0; i < N; ++i) {
        auto t0 = Clock::now();
        user.remove(*clusterBackend, uids[i]);
        auto t1 = Clock::now();
        removeTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    size_t rssAfterAll = getRSSKb();

    std::cout << "\n=== Cluster User CRUD Benchmark (" << N << " records) ===\n";
    printStats("CREATE", computeStats(createTimes), N);
    printStats("READ  ", computeStats(readTimes), N);
    printStats("LIST  ", computeStats(listTimes), 1);
    printStats("REMOVE", computeStats(removeTimes), N);

    std::cout << "\n  Memory (RSS kB):\n"
              << "    before       = " << rssBefore << "\n"
              << "    after create = " << rssAfterCreate << "\n"
              << "    after read   = " << rssAfterRead << "\n"
              << "    after all    = " << rssAfterAll << "\n"
              << "    delta create = " << (long long)rssAfterCreate - (long long)rssBefore << "\n";
}

// ═════════════════════════════════════════════════════════════
//  2.  ClusterBackend Group operations benchmark
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, GroupCreateReadBenchmark) {
    constexpr size_t N = 50;
    constexpr size_t MEMBERS_PER_GROUP = 10;

    std::vector<double> createTimes, readTimes;
    std::vector<uuid::uuid> gids(N);

    size_t rssBefore = getRSSKb();

    Group grp;
    for (size_t i = 0; i < N; ++i) {
        gids[i].generate();
        class authdb::GroupData g(gids[i]);
        g.setName("cgroup_" + std::to_string(i));
        for (size_t m = 0; m < MEMBERS_PER_GROUP; ++m) {
            uuid::uuid mid; mid.generate();
            g.addMember(mid);
        }

        auto t0 = Clock::now();
        grp.create(*clusterBackend, &g);
        auto t1 = Clock::now();
        createTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    size_t rssAfterCreate = getRSSKb();

    for (size_t i = 0; i < N; ++i) {
        class authdb::GroupData g(gids[i]);
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        grp.info(*clusterBackend, g, pos);
        auto t1 = Clock::now();
        readTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

        EXPECT_EQ(g.getName(), "cgroup_" + std::to_string(i));
    }

    std::cout << "\n=== Cluster Group Benchmark (" << N << " groups, "
              << MEMBERS_PER_GROUP << " members each) ===\n";
    printStats("CREATE", computeStats(createTimes), N);
    printStats("READ  ", computeStats(readTimes), N);
    std::cout << "  Memory delta = "
              << (long long)rssAfterCreate - (long long)rssBefore << " kB\n";
}

// ═════════════════════════════════════════════════════════════
//  3.  ClusterSession benchmark
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, ClusterSessionBenchmark) {
    // The waitForPeers() fix now clears critical_ before returning,
    // so the cluster should be ready.  Allow 3s grace for slow CI.
    if (authdb::g_Cluster) {
        for (int wait = 0; wait < 3 && authdb::g_Cluster->isCritical(); ++wait)
            std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    if (authdb::g_Cluster && authdb::g_Cluster->isCritical())
        GTEST_SKIP() << "Cluster is still critical after 3 s — cannot run session tests";

    constexpr size_t N = 50;

    // First create users in the file backend (createSessionData reads from backend)
    User user;
    uuid::uuid did;
    did.generate();
    std::vector<uuid::uuid> userIds(N);

    for (size_t i = 0; i < N; ++i) {
        userIds[i].generate();
        class authdb::UserData udat(userIds[i]);
        udat.setUserName("csessuser_" + std::to_string(i));
        udat.setPwHash("pw");
        user.create(*fileBackend, &udat);
    }

    ClusterSession sessions;
    std::vector<uuid::uuid> sids(N);
    std::vector<double> addTimes, getTimes, remTimes;

    size_t rssBefore = getRSSKb();

    // ── ADD sessions ──
    for (size_t i = 0; i < N; ++i) {
        auto t0 = Clock::now();
        const SessionData *sd = sessions.addSession(*fileBackend, did, userIds[i]);
        auto t1 = Clock::now();
        addTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

        ASSERT_NE(sd, nullptr);
        sd->getSid(sids[i]);
    }

    size_t rssAfterAdd = getRSSKb();

    // Small delay to allow async push to cluster store
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // ── GET sessions (local cache hit) ──
    for (size_t i = 0; i < N; ++i) {
        auto t0 = Clock::now();
        const SessionData *out = sessions.getSession(sids[i]);
        auto t1 = Clock::now();
        getTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

        EXPECT_NE(out, nullptr);
    }

    // ── REMOVE sessions ──
    for (size_t i = 0; i < N; ++i) {
        auto t0 = Clock::now();
        sessions.remSession(sids[i]);
        auto t1 = Clock::now();
        remTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    size_t rssAfterRemove = getRSSKb();

    std::cout << "\n=== Cluster Session Benchmark (" << N << " sessions) ===\n";
    printStats("ADD   ", computeStats(addTimes), N);
    printStats("GET   ", computeStats(getTimes), N);
    printStats("REMOVE", computeStats(remTimes), N);

    std::cout << "  Memory (RSS kB):\n"
              << "    before     = " << rssBefore << "\n"
              << "    after add  = " << rssAfterAdd << "\n"
              << "    after rem  = " << rssAfterRemove << "\n"
              << "    delta add  = " << (long long)rssAfterAdd - (long long)rssBefore << "\n"
              << "    delta rem  = " << (long long)rssAfterRemove - (long long)rssAfterAdd << "\n";
}

// ═════════════════════════════════════════════════════════════
//  4.  Data replication round-trip: write + read back via cluster
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, DataReplicationRoundTrip) {
    constexpr size_t N = 50;

    // Use a dedicated domain to avoid cache interference from other tests
    AuthBackend repBackend(authdb::ClusterStore, "", "repdomain");

    std::vector<uuid::uuid> uids(N);
    std::vector<double> writeTimes, readBackTimes;

    User user;
    for (size_t i = 0; i < N; ++i) {
        uids[i].generate();
        class authdb::UserData udat(uids[i]);
        udat.setUserName("repuser_" + std::to_string(i));
        udat.setPwHash("rephash_" + std::to_string(i));

        auto t0 = Clock::now();
        user.create(repBackend, &udat);
        auto t1 = Clock::now();
        writeTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    // Read back from the same cluster backend — this reflects the real
    // application path: writes go to the in-memory buffer + async push,
    // subsequent reads on the same domain pick up the cached data.
    for (size_t i = 0; i < N; ++i) {
        class authdb::UserData udat(uids[i]);
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        user.info(repBackend, udat, pos);
        auto t1 = Clock::now();
        readBackTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

        EXPECT_EQ(udat.getUsername(), "repuser_" + std::to_string(i));
    }

    std::cout << "\n=== Cluster Data Replication Round-Trip (" << N << " records) ===\n";
    printStats("WRITE      ", computeStats(writeTimes), N);
    printStats("READ-BACK  ", computeStats(readBackTimes), N);

    // Cleanup
    for (size_t i = 0; i < N; ++i) {
        user.remove(repBackend, uids[i]);
    }
}

// ═════════════════════════════════════════════════════════════
//  5.  Cluster vs File backend comparison
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, ClusterVsFileComparison) {
    constexpr size_t N = 50;

    std::vector<double> fileCreateTimes, clusterCreateTimes;
    std::vector<double> fileReadTimes, clusterReadTimes;

    // Use a separate file backend for comparison
    std::string cmpDbPath = tempDir() + "authdb_cmp_" + std::to_string(test_getpid()) + ".db";
    AuthBackend cmpFile(authdb::File, cmpDbPath, "cmpdomain");

    std::vector<uuid::uuid> fids(N), cids(N);

    User user;

    // ── File backend ──
    for (size_t i = 0; i < N; ++i) {
        fids[i].generate();
        class authdb::UserData udat(fids[i]);
        udat.setUserName("fuser_" + std::to_string(i));
        udat.setPwHash("pw");

        auto t0 = Clock::now();
        user.create(cmpFile, &udat);
        auto t1 = Clock::now();
        fileCreateTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    for (size_t i = 0; i < N; ++i) {
        class authdb::UserData udat(fids[i]);
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        user.info(cmpFile, udat, pos);
        auto t1 = Clock::now();
        fileReadTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    // ── Cluster backend (fresh domain for fair comparison) ──
    AuthBackend cmpCluster(authdb::ClusterStore, "", "cmpdomain");

    for (size_t i = 0; i < N; ++i) {
        cids[i].generate();
        class authdb::UserData udat(cids[i]);
        udat.setUserName("ccmpuser_" + std::to_string(i));
        udat.setPwHash("pw");

        auto t0 = Clock::now();
        user.create(cmpCluster, &udat);
        auto t1 = Clock::now();
        clusterCreateTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    for (size_t i = 0; i < N; ++i) {
        class authdb::UserData udat(cids[i]);
        size_t pos = sizeof(AuthHeader);

        auto t0 = Clock::now();
        user.info(cmpCluster, udat, pos);
        auto t1 = Clock::now();
        clusterReadTimes.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());
    }

    auto fCreate = computeStats(fileCreateTimes);
    auto cCreate = computeStats(clusterCreateTimes);
    auto fRead = computeStats(fileReadTimes);
    auto cRead = computeStats(clusterReadTimes);

    std::cout << "\n=== File vs Cluster Backend Comparison (" << N << " ops) ===\n"
              << "  CREATE:\n"
              << "    File    avg=" << fCreate.avg_us << " µs  total=" << fCreate.total_ms << " ms\n"
              << "    Cluster avg=" << cCreate.avg_us << " µs  total=" << cCreate.total_ms << " ms\n"
              << "    Ratio   = " << (cCreate.avg_us / std::max(fCreate.avg_us, 0.001)) << "x\n"
              << "  READ:\n"
              << "    File    avg=" << fRead.avg_us << " µs  total=" << fRead.total_ms << " ms\n"
              << "    Cluster avg=" << cRead.avg_us << " µs  total=" << cRead.total_ms << " ms\n"
              << "    Ratio   = " << (cRead.avg_us / std::max(fRead.avg_us, 0.001)) << "x\n";

    // Cleanup comparison DB
    test_unlink(cmpDbPath.c_str());
    test_unlink((cmpDbPath + ".lock").c_str());
}

// ═════════════════════════════════════════════════════════════
//  6.  Prefetch never blocks (non-blocking async retrieve)
//
//  Bug: The old implementation did a synchronous 5s wait on
//  client->retrieve() inside fetchFromCluster(). Every other
//  request would hit the 5s timeout, blocking the HTTP thread.
//  Fix: retrieve is fire-and-forget; the result is picked up
//  on the next prefetch() call.  Each prefetch() must complete
//  in well under 1 second.
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, PrefetchNeverBlocks) {
    constexpr size_t ROUNDS = 20;
    constexpr double MAX_MS = 1000.0;  // must finish in <1s (old bug: 5s)

    std::vector<double> times_ms;

    for (size_t i = 0; i < ROUNDS; ++i) {
        // Each round: fresh ClusterBackend (simulates a new HTTP request).
        // Force a refresh so it actually attempts a cluster fetch, not just cache.
        AuthBackend cb(authdb::ClusterStore, "", "prefetch_test_domain");

        auto t0 = Clock::now();
        cb.lock();
        cb.unlock();
        auto t1 = Clock::now();

        double ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
        times_ms.push_back(ms);
    }

    double maxTime = *std::max_element(times_ms.begin(), times_ms.end());
    double avgTime = std::accumulate(times_ms.begin(), times_ms.end(), 0.0) / times_ms.size();

    std::cout << "\n=== Prefetch Non-Blocking Test (" << ROUNDS << " rounds) ===\n"
              << "  avg = " << avgTime << " ms\n"
              << "  max = " << maxTime << " ms  (limit: " << MAX_MS << " ms)\n";

    // The critical assertion: no single prefetch may block ≥1s.
    // Before the fix, ~50% would hit the 5s timeout.
    EXPECT_LT(maxTime, MAX_MS)
        << "prefetch() blocked for " << maxTime << " ms — async retrieve may be synchronous";
}

// ═════════════════════════════════════════════════════════════
//  7.  Revision guard prevents stale data overwrite
//
//  Bug: If a recovering cluster node returned a manifest with
//  an older revision, fetchFromCluster() would blindly replace
//  the local cache → data loss.
//  Fix: fetchFromCluster() compares the manifest revision to
//  the cached revision and discards stale data.
// ═════════════════════════════════════════════════════════════

TEST_F(ClusterPerformanceTest, RevisionGuardNoDataLoss) {
    const std::string domain = "revguard_" + std::to_string(test_getpid());

    // Phase 1: Create 10 users via cluster
    constexpr size_t N = 10;
    std::vector<uuid::uuid> uids(N);
    {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        User user;
        for (size_t i = 0; i < N; ++i) {
            uids[i].generate();
            class authdb::UserData udat(uids[i]);
            udat.setUserName("revuser_" + std::to_string(i));
            udat.setPwHash("pw" + std::to_string(i));
            user.create(cb, &udat);
        }
    }

    // Small delay to let async push propagate
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Phase 2: Read back revision and user count
    size_t rev_after_create = 0;
    size_t user_count = 0;
    {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        cb.lock();
        rev_after_create = cb.getRevesion();
        User user;
        std::vector<uuid::uuid> listed;
        user.list(cb, listed);
        user_count = listed.size();
        cb.unlock();
    }

    EXPECT_EQ(user_count, N)
        << "Expected " << N << " users but found " << user_count;
    EXPECT_GT(rev_after_create, 0u)
        << "Revision should be > 0 after creating users";

    // Phase 3: Force multiple rapid prefetch cycles — revision must never decrease
    size_t min_rev = rev_after_create;
    size_t max_rev = rev_after_create;
    size_t min_users = user_count;

    for (int round = 0; round < 10; ++round) {
        AuthBackend cb(authdb::ClusterStore, "", domain);
        cb.lock();
        size_t rev = cb.getRevesion();
        User user;
        std::vector<uuid::uuid> listed;
        user.list(cb, listed);
        cb.unlock();

        if (rev < min_rev) min_rev = rev;
        if (rev > max_rev) max_rev = rev;
        if (listed.size() < min_users) min_users = listed.size();
    }

    std::cout << "\n=== Revision Guard Test ===\n"
              << "  rev after create = " << rev_after_create << "\n"
              << "  rev range        = [" << min_rev << ", " << max_rev << "]\n"
              << "  min users seen   = " << min_users << " (expected " << N << ")\n";

    // Revision must never go backward
    EXPECT_GE(min_rev, rev_after_create)
        << "Revision went backward: saw " << min_rev << " but expected >= " << rev_after_create
        << " — stale manifest was accepted";

    // Users must never disappear
    EXPECT_EQ(min_users, N)
        << "Lost users: saw " << min_users << " but expected " << N
        << " — stale manifest overwrote cache";
}
