/*
 * Unit tests for AuthBackend (file-backed):
 * - Write/read records, CRUD via high-level API
 * - Revision tracking, vacuum
 * - Guard RAII locking
 * - Data integrity after write-read cycles
 *
 * NOTE: authdb defines enum values (UserData=1, GroupData=3, etc.) that
 * shadow the class names. We must use the 'class' keyword to disambiguate.
 */
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
#include <uuidp.h>
#include <unistd.h>
#include <sys/stat.h>

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

namespace authdb { std::mutex AuthClientLock; }

using authdb::AuthBackend;
using authdb::AuthHeader;
using authdb::User;
using authdb::Group;
using authdb::Gpo;
using authdb::Domain;
using authdb::GpoData;

class FileBackendTest : public ::testing::Test {
protected:
    std::string dbPath;
    AuthBackend *backend = nullptr;

    void SetUp() override {
        dbPath = "/tmp/authdb_test_backend_" + std::to_string(getpid()) + ".db";
        backend = new AuthBackend(authdb::File, dbPath, "testdomain");
    }

    void TearDown() override {
        delete backend;
        unlink(dbPath.c_str());
        unlink((dbPath + ".lock").c_str());
    }
};

// ── Guard RAII locking ──

TEST_F(FileBackendTest, GuardExclusiveLock) {
    {
        AuthBackend::Guard guard(*backend, AuthBackend::Exclusive);
        // Should not deadlock or throw
    }
    SUCCEED();
}

TEST_F(FileBackendTest, GuardSharedLock) {
    {
        AuthBackend::Guard guard(*backend, AuthBackend::Shared);
    }
    SUCCEED();
}

// ── User CRUD via backend ──

TEST_F(FileBackendTest, CreateAndReadUser) {
    uuid::uuid uid;
    uid.generate();

    // Create
    {
        class authdb::UserData udat(uid);
        udat.setUserName("testuser");
        udat.setFirstName("Test");
        udat.setLastName("User");
        udat.setMail("test@example.com");
        udat.setPwHash("testhash123");
        User user;
        user.create(*backend, &udat);
    }

    // Read back
    {
        class authdb::UserData udat(uid);
        size_t pos = sizeof(AuthHeader);
        User user;
        user.info(*backend, udat, pos);
        EXPECT_EQ(udat.getUsername(), "testuser");
        EXPECT_EQ(udat.getFirstname(), "Test");
        EXPECT_EQ(udat.getLastname(), "User");
        EXPECT_EQ(udat.getMail(), "test@example.com");
    }
}

TEST_F(FileBackendTest, UserList) {
    User user;
    for (int i = 0; i < 5; ++i) {
        uuid::uuid uid;
        uid.generate();
        class authdb::UserData udat(uid);
        udat.setUserName("user_" + std::to_string(i));
        udat.setPwHash("pw");
        user.create(*backend, &udat);
    }

    std::vector<uuid::uuid> uuids;
    user.list(*backend, uuids);
    EXPECT_EQ(uuids.size(), 5u);
}

TEST_F(FileBackendTest, UserRemove) {
    uuid::uuid uid;
    uid.generate();
    class authdb::UserData udat(uid);
    udat.setUserName("removeuser");
    udat.setPwHash("pw");
    User user;
    user.create(*backend, &udat);

    user.remove(*backend, uid);

    std::vector<uuid::uuid> uuids;
    user.list(*backend, uuids);
    EXPECT_EQ(uuids.size(), 0u);
}

// ── Group CRUD ──

TEST_F(FileBackendTest, CreateGroupWithMembers) {
    uuid::uuid gid;
    gid.generate();

    uuid::uuid m1, m2;
    m1.generate();
    m2.generate();

    {
        class authdb::GroupData g(gid);
        g.setName("testgroup");
        g.addMember(m1);
        g.addMember(m2);
        Group grp;
        grp.create(*backend, &g);
    }

    {
        class authdb::GroupData g(gid);
        size_t pos = sizeof(AuthHeader);
        Group grp;
        grp.info(*backend, g, pos);
        EXPECT_EQ(g.getName(), "testgroup");
        EXPECT_TRUE(g.isMember(m1));
        EXPECT_TRUE(g.isMember(m2));
    }
}

// ── GPO CRUD ──

TEST_F(FileBackendTest, CreateAndReadGpo) {
    uuid::uuid gpoid;
    gpoid.generate();

    {
        authdb::GpoData gpo(gpoid);
        gpo.setName("AllowAdmin");
        gpo.setDesc("Allows admin panel access");
        gpo.setGpoValue(true);

        uuid::uuid a1;
        a1.generate();
        std::vector<uuid::uuid> allowed = {a1};
        gpo.setAllowed(allowed);

        Gpo gpoMgr;
        gpoMgr.create(*backend, &gpo);
    }

    {
        authdb::GpoData gpo(gpoid);
        size_t pos = sizeof(AuthHeader);
        Gpo gpoMgr;
        gpoMgr.info(*backend, gpo, pos);
        EXPECT_STREQ(gpo.getName(), "AllowAdmin");
        EXPECT_STREQ(gpo.getDesc(), "Allows admin panel access");
        EXPECT_TRUE(gpo.getGpoValue());

        std::vector<uuid::uuid> allowed;
        gpo.getAlowed(allowed);
        EXPECT_EQ(allowed.size(), 1u);
    }
}

// ── Domain CRUD ──

TEST_F(FileBackendTest, CreateAndReadDomain) {
    uuid::uuid did;
    did.generate();

    {
        class authdb::DomainData d(did);
        d.setDomainName("corp.example.com");
        d.setStorageType(0);
        d.setStorageOptions("/tmp/corp.db");
        Domain dom;
        dom.create(*backend, &d);
    }

    {
        class authdb::DomainData d(did);
        size_t pos = sizeof(AuthHeader);
        Domain dom;
        dom.info(*backend, d, pos);
        EXPECT_EQ(d.getDomainName(), "corp.example.com");
        EXPECT_EQ(d.getStorageType(), 0);
        EXPECT_EQ(d.getStorageOptions(), "/tmp/corp.db");
    }
}

// ── Revision tracking ──

TEST_F(FileBackendTest, RevisionIncrementsOnWrite) {
    size_t rev1, rev2;
    {
        AuthBackend::Guard guard(*backend, AuthBackend::Exclusive);
        rev1 = backend->getRevesion();
    }

    uuid::uuid uid;
    uid.generate();
    class authdb::UserData udat(uid);
    udat.setUserName("revtest");
    udat.setPwHash("pw");
    User user;
    user.create(*backend, &udat);

    {
        AuthBackend::Guard guard(*backend, AuthBackend::Shared);
        rev2 = backend->getRevesion();
    }
    EXPECT_GT(rev2, rev1);
}

// ── Vacuum ──

TEST_F(FileBackendTest, VacuumAfterDelete) {
    User user;

    // Create several users
    std::vector<uuid::uuid> uids;
    for (int i = 0; i < 5; ++i) {
        uuid::uuid uid;
        uid.generate();
        class authdb::UserData udat(uid);
        udat.setUserName("vacuser_" + std::to_string(i));
        udat.setPwHash("pw");
        user.create(*backend, &udat);
        uids.push_back(uid);
    }

    // Delete some
    user.remove(*backend, uids[1]);
    user.remove(*backend, uids[3]);

    // Vacuum
    {
        AuthBackend::Guard guard(*backend, AuthBackend::Exclusive);
        backend->vacuum();
    }

    // Verify remaining
    std::vector<uuid::uuid> remaining;
    user.list(*backend, remaining);
    EXPECT_EQ(remaining.size(), 3u);
}

// ── Data integrity: write many records, read all back ──

TEST_F(FileBackendTest, BulkWriteReadIntegrity) {
    User user;
    std::vector<std::pair<uuid::uuid, std::string>> created;

    for (int i = 0; i < 20; ++i) {
        uuid::uuid uid;
        uid.generate();
        std::string name = "bulk_" + std::to_string(i);
        class authdb::UserData udat(uid);
        udat.setUserName(name);
        udat.setPwHash("hash_" + std::to_string(i));
        udat.setMail(name + "@test.de");
        user.create(*backend, &udat);
        created.emplace_back(uid, name);
    }

    for (auto &[uid, name] : created) {
        class authdb::UserData udat(uid);
        size_t pos = sizeof(AuthHeader);
        user.info(*backend, udat, pos);
        EXPECT_EQ(udat.getUsername(), name);
        EXPECT_EQ(udat.getMail(), name + "@test.de");
    }
}

TEST_F(FileBackendTest, DataSafetyAndVacuum) {
    User user;
    std::vector<std::pair<uuid::uuid, std::string>> all_users;

    for (int i = 0; i < 20; ++i) {
        uuid::uuid uid;
        uid.generate();
        std::string name = "safeuser_" + std::to_string(i);
        class authdb::UserData udat(uid);
        udat.setUserName(name);
        udat.setPwHash("supersecret_" + std::to_string(i));
        udat.setMail(name + "@example.com");
        udat.setFirstName("First" + std::to_string(i));
        udat.setLastName("Last" + std::to_string(i));
        user.create(*backend, &udat);
        all_users.emplace_back(uid, name);
    }
    
    // Check file size before delete
    struct stat st1, st2, st3;
    stat(dbPath.c_str(), &st1);

    // Delete half of them (even indices)
    for(size_t i = 0; i < all_users.size(); i += 2) {
        user.remove(*backend, all_users[i].first);
    }

    stat(dbPath.c_str(), &st2);
    // Size should not decrease before vacuum
    EXPECT_GE(st2.st_size, st1.st_size);

    // Run Vacuum
    {
        AuthBackend::Guard guard(*backend, AuthBackend::Exclusive);
        backend->vacuum();
    }

    stat(dbPath.c_str(), &st3);
    // Size should be strictly smaller than before vacuum
    EXPECT_LT(st3.st_size, st2.st_size);

    // Verify exactly the odd indices remain, with ALL valid data perfect
    std::vector<uuid::uuid> remaining;
    user.list(*backend, remaining);
    EXPECT_EQ(remaining.size(), 10u);

    for (size_t i = 1; i < all_users.size(); i += 2) {
        class authdb::UserData udat(all_users[i].first);
        size_t pos = sizeof(AuthHeader);
        user.info(*backend, udat, pos);
        
        EXPECT_EQ(udat.getUsername(), "safeuser_" + std::to_string(i));
        EXPECT_EQ(udat.getMail(), "safeuser_" + std::to_string(i) + "@example.com");
        EXPECT_EQ(udat.getFirstname(), "First" + std::to_string(i));
        EXPECT_EQ(udat.getLastname(), "Last" + std::to_string(i));
    }
}
