/**
 * @file sqlite3db_unit-t.cpp
 * @brief Unit tests for SQLite3DB — the foundational database wrapper.
 *
 * Tests cover: execute(), execute_statement(), return_one_int(),
 * check_table_structure(), build_table(), check_and_build_table(),
 * SQLite3_row::get_size(), and locking primitives.
 *
 * All tests use an in-memory SQLite3 database — no external deps.
 */

#include "tap.h"
#include "test_globals.h"
#include "test_init.h"
#include "proxysql.h"
#include "sqlite3db.h"

#include <cstring>
#include <string>

// ============================================================
// SQLite3DB core operations
// ============================================================

static SQLite3DB *db = nullptr;

static void setup_db() {
    db = new SQLite3DB();
    db->open((char *)":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX);
}

static void teardown_db() {
    delete db;
    db = nullptr;
}

static void test_open_and_execute() {
    bool rc = db->execute("CREATE TABLE test_t (id INTEGER PRIMARY KEY, name TEXT)");
    ok(rc == true, "execute: CREATE TABLE succeeds (returns true)");
}

static void test_execute_insert_select() {
    db->execute("INSERT INTO test_t (id, name) VALUES (1, 'alice')");
    db->execute("INSERT INTO test_t (id, name) VALUES (2, 'bob')");

    char *error = nullptr;
    SQLite3_result *result = db->execute_statement("SELECT COUNT(*) FROM test_t", &error);
    ok(result != nullptr, "execute_statement: returns result for SELECT");
    ok(result != nullptr && result->rows_count == 1, "execute_statement: COUNT(*) returns 1 row");
    if (result && result->rows_count > 0) {
        ok(strcmp(result->rows[0]->fields[0], "2") == 0,
           "execute_statement: COUNT(*) = 2");
    } else {
        ok(false, "execute_statement: COUNT(*) = 2 (no result)");
    }
    if (error) free(error);
    delete result;
}

// ============================================================
// return_one_int() — convenience wrapper
// ============================================================

static void test_return_one_int() {
    int val = db->return_one_int("SELECT 42");
    ok(val == 42, "return_one_int: SELECT 42 returns 42");
}

static void test_return_one_int_from_table() {
    int val = db->return_one_int("SELECT COUNT(*) FROM test_t");
    ok(val == 2, "return_one_int: COUNT(*) from test_t = 2");
}

static void test_return_one_int_no_rows() {
    int val = db->return_one_int("SELECT id FROM test_t WHERE id = 999");
    ok(val == 0, "return_one_int: no rows returns 0");
}

// ============================================================
// check_table_structure() — schema validation
// ============================================================

static void test_check_table_structure_match() {
    // The CREATE statement we used
    int match = db->check_table_structure(
        (char *)"test_t",
        (char *)"CREATE TABLE test_t (id INTEGER PRIMARY KEY, name TEXT)"
    );
    ok(match == 1, "check_table_structure: matching schema returns 1");
}

static void test_check_table_structure_mismatch() {
    int match = db->check_table_structure(
        (char *)"test_t",
        (char *)"CREATE TABLE test_t (id INTEGER, extra_col TEXT)"
    );
    ok(match == 0, "check_table_structure: mismatched schema returns 0");
}

static void test_check_table_structure_nonexistent() {
    int match = db->check_table_structure(
        (char *)"nonexistent_table",
        (char *)"CREATE TABLE nonexistent_table (id INT)"
    );
    ok(match == 0, "check_table_structure: nonexistent table returns 0");
}

// ============================================================
// build_table() / check_and_build_table()
// ============================================================

static void test_build_table() {
    bool rc = db->build_table(
        (char *)"new_table",
        (char *)"CREATE TABLE new_table (val TEXT)",
        false  // don't drop if exists
    );
    ok(rc == true, "build_table: creates new table successfully");

    // Verify it exists
    int count = db->return_one_int("SELECT COUNT(*) FROM new_table");
    ok(count == 0, "build_table: new table is empty");
}

static void test_build_table_drop_recreate() {
    // Insert data first
    db->execute("INSERT INTO new_table VALUES ('data')");
    int before = db->return_one_int("SELECT COUNT(*) FROM new_table");
    ok(before == 1, "build_table: table has 1 row before drop");

    bool rc = db->build_table(
        (char *)"new_table",
        (char *)"CREATE TABLE new_table (val TEXT)",
        true  // drop and recreate
    );
    ok(rc == true, "build_table: drop+recreate succeeds");

    int after = db->return_one_int("SELECT COUNT(*) FROM new_table");
    ok(after == 0, "build_table: table is empty after drop+recreate");
}

static void test_check_and_build_table_creates() {
    db->check_and_build_table(
        (char *)"auto_table",
        (char *)"CREATE TABLE auto_table (x INT)"
    );
    int count = db->return_one_int("SELECT COUNT(*) FROM auto_table");
    ok(count == 0, "check_and_build_table: creates table if not exists");
}

static void test_check_and_build_table_idempotent() {
    db->execute("INSERT INTO auto_table VALUES (1)");
    db->check_and_build_table(
        (char *)"auto_table",
        (char *)"CREATE TABLE auto_table (x INT)"
    );
    int count = db->return_one_int("SELECT COUNT(*) FROM auto_table");
    ok(count == 1, "check_and_build_table: idempotent -- data preserved");
}

// ============================================================
// SQLite3_row — row operations
// ============================================================

static void test_sqlite3_row_get_size() {
    char *error = nullptr;
    SQLite3_result *result = db->execute_statement("SELECT id, name FROM test_t WHERE id = 1", &error);
    ok(result != nullptr && result->rows_count == 1, "row_get_size: got 1 row");
    if (result && result->rows_count > 0) {
        unsigned long long sz = result->rows[0]->get_size();
        ok(sz > 0, "row_get_size: size is non-zero");
    } else {
        ok(false, "row_get_size: size is non-zero (no result)");
    }
    if (error) free(error);
    delete result;
}

// ============================================================
// Locking operations (basic sanity)
// ============================================================

static void test_rdlock_unlock() {
    // Just verify no crash/deadlock on lock+unlock
    db->rdlock();
    db->rdunlock();
    ok(true, "rdlock/rdunlock: no crash");
}

static void test_wrlock_unlock() {
    db->wrlock();
    db->wrunlock();
    ok(true, "wrlock/wrunlock: no crash");
}

int main() {
    plan(21);
    test_init_minimal();

    setup_db();

    test_open_and_execute();                // 1
    test_execute_insert_select();           // 3
    test_return_one_int();                  // 1
    test_return_one_int_from_table();       // 1
    test_return_one_int_no_rows();          // 1
    test_check_table_structure_match();     // 1
    test_check_table_structure_mismatch();  // 1
    test_check_table_structure_nonexistent(); // 1
    test_build_table();                     // 2
    test_build_table_drop_recreate();       // 3
    test_check_and_build_table_creates();   // 1
    test_check_and_build_table_idempotent();// 1
    test_sqlite3_row_get_size();            // 2
    test_rdlock_unlock();                   // 1
    test_wrlock_unlock();                   // 1

    teardown_db();
    test_cleanup_minimal();
    return exit_status();
}
