#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 {
    extern std::mutex AuthClientLock;
    extern Cluster *g_Cluster;
}

using namespace authdb;

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 std::string g_certFile;
static std::string g_keyFile;

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

    netplus::CertGenConfig cfg;
    cfg.commonName = "authdb-test";
    cfg.rsaBits    = 2048;
    cfg.validDays  = 1;

    netplus::CertKeyPair pair;
    if (!netplus::generateSelfSignedCert(cfg, pair)) {
        return false;
    }

    if (!netplus::writePemFile(g_certFile, pair.certPem)) return false;
    if (!netplus::writeDerFile(g_keyFile, pair.keyDer)) 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());
}

static constexpr int NUM_NODES = 3;

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

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

        int basePort = 24433 + (test_getpid() % 10000);
        for (int i = 0; i < NUM_NODES; ++i)
            basePorts[i] = basePort + i;

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

        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);
        }

        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 = "testops";
            cfg.client_key = "testkeyops";
            cfg.store_path = "";  // memory-only for fast testing
            cfg.data_blocks = 2;
            cfg.parity_blocks = 1;
            cfg.peers = allPeers;

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

        authdb::g_Cluster = clusters[0];

        size_t online = clusters[0]->waitForPeers(10);
        clusterReady = (online >= 2);
    }

    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());
        cleanupTestCerts();
    }

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

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

// ── Test Session Push & Fetch ──
TEST_F(ClusterOpsTest, SessionPushAndFetch) {
    uuid::uuid sid, uid, did;
    sid.generate();
    uid.generate();
    did.generate();
    std::vector<uuid::uuid> members;

    SessionData sd(sid, uid, did, members);

    // Push via node 0
    clusters[0]->pushSession(sd);

    // Give it a moment to replicate
    std::this_thread::sleep_for(std::chrono::milliseconds(200));

    // Try fetching from node 0
    uuid::uuid f_sid, f_uid, f_did;
    std::vector<uuid::uuid> f_members;
    std::string f_username;
    std::vector<std::pair<uuid::uuid, bool>> f_gpos;
    
    bool ok = clusters[0]->fetchSessionBySid(sid, f_sid, f_uid, f_did, f_members, f_username, f_gpos);
    EXPECT_TRUE(ok);
    EXPECT_EQ(f_sid, sid);
    EXPECT_EQ(f_uid, uid);
    EXPECT_EQ(f_did, did);

    // Wait and scrub 
    auto res = clusters[0]->scrub();
    EXPECT_GE(res.groups_checked, 0u);
}

// ── Test Data Replication & Scrub ──
TEST_F(ClusterOpsTest, ReplicateOpAndScrub) {
    uuid::uuid recId;
    recId.generate();

    // Replicate an op on node 0
    clusters[0]->replicateOp(authdb::OpType::Create, "testdomain", recId, 1 /* UserData */, nullptr);

    // Provide some time for push
    std::this_thread::sleep_for(std::chrono::milliseconds(200));

    // Run scrub on node 0
    auto res = clusters[0]->scrub();
    
    // We expect some repaired or checked groups, at least 0 if it was skipped
    EXPECT_GE(res.groups_checked, 0u);
}

// ── Test Rebalance ──
TEST_F(ClusterOpsTest, RebalanceOp) {
    auto &client = clusters[0]->getClient();
    auto rb = client->rebalance();
    EXPECT_GE(rb.rebalanced, 0u);
}
