/*
 * Unit tests for AuthData: record creation, linked list integrity,
 * setAuthData/getAuthData template functions, copy semantics, and
 * memory-leak-free destruction.
 */
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <uuidp.h>

#include "authdb.h"
#include "types.h"
#include "exception.h"

using namespace authdb;

// ── AuthData construction / destruction ──

TEST(AuthData, ConstructWithUuid) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);
    // empty() returns true because Data.data is nullptr (no field data yet)
    EXPECT_TRUE(dat.empty());
    EXPECT_EQ(dat.next(), nullptr);
    EXPECT_EQ(memcmp(dat.Data.ruid, id.value, 16), 0);
}

TEST(AuthData, DefaultRecordIsEmpty) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);
    EXPECT_EQ(dat.Data.type, EmptyData);
    EXPECT_EQ(dat.Data.storage, EmptyStorage);
    EXPECT_EQ(dat.Data.datasize, 0u);
    EXPECT_EQ(dat.Data.data, nullptr);
}

TEST(AuthData, CopyConstructor) {
    uuid::uuid id;
    id.generate();
    AuthData orig(id);
    // Populate via setAuthData so copy has real data to work with
    AuthData::Record *rec = nullptr;
    const char *val = "hello";
    setAuthData<char>(UserData, TextStorage, &orig, &rec, "testfield", val, strlen(val)+1);

    AuthData copy(orig);
    EXPECT_EQ(memcmp(copy.Data.ruid, id.value, 16), 0);
    EXPECT_EQ(copy.Data.type, UserData);
    EXPECT_EQ(copy.Data.storage, TextStorage);
    EXPECT_EQ(copy.Data.datasize, strlen(val)+1);
    EXPECT_STREQ(copy.Data.data, "hello");
}

// ── Linked list: append records ──

TEST(AuthData, AppendCreatesLinkedList) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    // Records need data for append to create new nodes;
    // when datasize==0, append fills the current node instead of appending
    AuthData::Record r1;
    r1.type = UserData;
    r1.storage = TextStorage;
    strcpy(r1.fieldname, "field1");
    r1.datasize = 5;
    r1.data = new char[5];
    strcpy(r1.data, "val1");
    dat.append(r1);

    AuthData::Record r2;
    r2.type = GroupData;
    r2.storage = BinaryStorage;
    strcpy(r2.fieldname, "field2");
    r2.datasize = 5;
    r2.data = new char[5];
    strcpy(r2.data, "val2");
    dat.append(r2);

    // First append fills the root node (dat); second creates dat->_next
    EXPECT_EQ(dat.Data.type, UserData);
    EXPECT_STREQ(dat.Data.fieldname, "field1");

    const AuthData *n1 = dat.next();
    ASSERT_NE(n1, nullptr);
    EXPECT_EQ(n1->Data.type, GroupData);
    EXPECT_STREQ(n1->Data.fieldname, "field2");

    EXPECT_EQ(n1->next(), nullptr);
}

// ── setAuthData / getAuthData template functions ──

TEST(AuthDataTemplate, SetAndGetTextData) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    AuthData::Record *rec = nullptr;
    const char *text = "hello world";
    setAuthData<char>(UserData, TextStorage, &dat, &rec, "username",
                      text, strlen(text) + 1);

    ASSERT_NE(rec, nullptr);
    EXPECT_EQ(rec->type, UserData);
    EXPECT_EQ(rec->storage, TextStorage);
    EXPECT_STREQ(rec->fieldname, "username");
    EXPECT_EQ(rec->datasize, strlen(text) + 1);
    EXPECT_STREQ(rec->data, "hello world");
}

TEST(AuthDataTemplate, SetOverwritesExistingField) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    AuthData::Record *rec = nullptr;
    const char *text1 = "first";
    setAuthData<char>(UserData, TextStorage, &dat, &rec, "username",
                      text1, strlen(text1) + 1);

    // Overwrite the same field
    rec = nullptr;  // force re-search
    const char *text2 = "second";
    setAuthData<char>(UserData, TextStorage, &dat, &rec, "username",
                      text2, strlen(text2) + 1);

    ASSERT_NE(rec, nullptr);
    EXPECT_STREQ(rec->data, "second");
}

TEST(AuthDataTemplate, GetMissingFieldThrows) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    AuthData::Record *rec = nullptr;
    EXPECT_THROW(
        getAuthData<char>(UserData, TextStorage, &dat, &rec, "nonexistent"),
        AuthBackendError
    );
    EXPECT_EQ(rec, nullptr);
}

TEST(AuthDataTemplate, SetAndGetBinaryData) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    unsigned char binary[] = {0x00, 0xFF, 0xAB, 0xCD, 0x01};
    AuthData::Record *rec = nullptr;
    setAuthData<unsigned char>(UserData, BinaryStorage, &dat, &rec, "avatar",
                               binary, sizeof(binary));

    AuthData::Record *rec2 = nullptr;
    const unsigned char *result = getAuthData<unsigned char>(
        UserData, BinaryStorage, &dat, &rec2, "avatar");

    ASSERT_NE(result, nullptr);
    EXPECT_EQ(memcmp(result, binary, sizeof(binary)), 0);
}

TEST(AuthDataTemplate, SetIntData) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    int value = 42;
    AuthData::Record *rec = nullptr;
    setAuthData<int>(UserData, IntStorage, &dat, &rec, "count",
                     &value, 1);

    AuthData::Record *rec2 = nullptr;
    const int *result = getAuthData<int>(UserData, IntStorage, &dat, &rec2, "count");
    ASSERT_NE(result, nullptr);
    EXPECT_EQ(*result, 42);
}

// ── Fieldname boundary (255 chars) ──

TEST(AuthData, FieldNameMaxLength) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    // 254 chars + null terminator fits in fieldname[255]
    std::string longName(254, 'A');
    AuthData::Record *rec = nullptr;
    const char *text = "val";
    setAuthData<char>(UserData, TextStorage, &dat, &rec, longName.c_str(),
                      text, strlen(text) + 1);

    ASSERT_NE(rec, nullptr);
    EXPECT_STREQ(rec->fieldname, longName.c_str());
}

// ── Multiple records with different types on same AuthData ──

TEST(AuthDataTemplate, MultipleFieldsCoexist) {
    uuid::uuid id;
    id.generate();
    AuthData dat(id);

    AuthData::Record *r1 = nullptr;
    AuthData::Record *r2 = nullptr;
    AuthData::Record *r3 = nullptr;

    const char *name = "alice";
    const char *mail = "alice@test.de";
    int age = 30;

    setAuthData<char>(UserData, TextStorage, &dat, &r1, "username", name, strlen(name) + 1);
    setAuthData<char>(UserData, TextStorage, &dat, &r2, "mail", mail, strlen(mail) + 1);
    setAuthData<int>(UserData, IntStorage, &dat, &r3, "age", &age, 1);

    AuthData::Record *g1 = nullptr, *g2 = nullptr, *g3 = nullptr;
    EXPECT_STREQ(getAuthData<char>(UserData, TextStorage, &dat, &g1, "username"), "alice");
    EXPECT_STREQ(getAuthData<char>(UserData, TextStorage, &dat, &g2, "mail"), "alice@test.de");
    EXPECT_EQ(*getAuthData<int>(UserData, IntStorage, &dat, &g3, "age"), 30);
}

// ── Destruction with allocated data (memory leak detection via ASan) ──

TEST(AuthData, DestructionFreesAllocatedData) {
    uuid::uuid id;
    id.generate();

    {
        AuthData dat(id);
        AuthData::Record *rec = nullptr;
        const char *text = "leak test payload data string";
        setAuthData<char>(UserData, TextStorage, &dat, &rec, "field",
                          text, strlen(text) + 1);

        // Append more nodes to test linked list cleanup
        for (int i = 0; i < 10; ++i) {
            AuthData::Record r;
            r.type = UserData;
            r.storage = TextStorage;
            std::string fname = "extra_" + std::to_string(i);
            strcpy(r.fieldname, fname.c_str());
            r.datasize = 64;
            r.data = new char[64];
            memset(r.data, 'X', 64);
            dat.append(r);
        }
    }
    // If ASan is enabled, this will catch leaks
    SUCCEED();
}
