/**
 * @file genai_module-t.cpp
 * @brief TAP test for the GenAI module
 *
 * This test verifies the functionality of the GenAI (Generative AI) module in ProxySQL.
 * It tests:
 *   - LOAD/SAVE commands for GenAI variables across all variants
 *   - Variable access (SET and SELECT) for genai-var1 and genai-var2
 *   - Variable persistence across storage layers (memory, disk, runtime)
 *   - CHECKSUM commands for GenAI variables
 *   - SHOW VARIABLES for GenAI module
 *
 * @date 2025-01-08
 */

#include <algorithm>
#include <string>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <vector>
#include <tuple>

#include "mysql.h"
#include "mysqld_error.h"

#include "tap.h"
#include "command_line.h"
#include "utils.h"

using std::string;

/**
 * @brief Helper function to add LOAD/SAVE command variants for GenAI module
 *
 * This function generates all the standard LOAD/SAVE command variants that
 * ProxySQL supports for module variables. It follows the same pattern used
 * for other modules like MYSQL, PGSQL, etc.
 *
 * @param queries Vector to append the generated commands to
 */
void add_genai_load_save_commands(std::vector<std::string>& queries) {
	// LOAD commands - Memory variants
	queries.push_back("LOAD GENAI VARIABLES TO MEMORY");
	queries.push_back("LOAD GENAI VARIABLES TO MEM");

	// LOAD from disk
	queries.push_back("LOAD GENAI VARIABLES FROM DISK");

	// LOAD from memory
	queries.push_back("LOAD GENAI VARIABLES FROM MEMORY");
	queries.push_back("LOAD GENAI VARIABLES FROM MEM");

	// LOAD to runtime
	queries.push_back("LOAD GENAI VARIABLES TO RUNTIME");
	queries.push_back("LOAD GENAI VARIABLES TO RUN");

	// SAVE from memory
	queries.push_back("SAVE GENAI VARIABLES FROM MEMORY");
	queries.push_back("SAVE GENAI VARIABLES FROM MEM");

	// SAVE to disk
	queries.push_back("SAVE GENAI VARIABLES TO DISK");

	// SAVE to memory
	queries.push_back("SAVE GENAI VARIABLES TO MEMORY");
	queries.push_back("SAVE GENAI VARIABLES TO MEM");

	// SAVE from runtime
	queries.push_back("SAVE GENAI VARIABLES FROM RUNTIME");
	queries.push_back("SAVE GENAI VARIABLES FROM RUN");
}

/**
 * @brief Get the value of a GenAI variable as a string
 *
 * @param admin MySQL connection to admin interface
 * @param var_name Variable name (without genai- prefix)
 * @return std::string The variable value, or empty string on error
 */
std::string get_genai_variable(MYSQL* admin, const std::string& var_name) {
	std::string query = "SELECT @@genai-" + var_name;
	if (mysql_query(admin, query.c_str()) != 0) {
		return "";
	}

	MYSQL_RES* res = mysql_store_result(admin);
	if (!res) {
		return "";
	}

	MYSQL_ROW row = mysql_fetch_row(res);
	std::string value = row && row[0] ? row[0] : "";

	mysql_free_result(res);
	return value;
}

/**
 * @brief Test variable access operations (SET and SELECT)
 *
 * Tests setting and retrieving GenAI variables to ensure they work correctly.
 */
int test_variable_access(MYSQL* admin) {
	int test_num = 0;

	// Test 1: Get default value of genai-var1
	std::string var1_default = get_genai_variable(admin, "var1");
	ok(var1_default == "default_value_1",
	   "Default value of genai-var1 is 'default_value_1', got '%s'", var1_default.c_str());

	// Test 2: Get default value of genai-var2
	std::string var2_default = get_genai_variable(admin, "var2");
	ok(var2_default == "100", 
	   "Default value of genai-var2 is '100', got '%s'", var2_default.c_str());

	// Test 3: Set genai-var1 to a new value
	MYSQL_QUERY(admin, "SET genai-var1='test_value_123'");
	std::string var1_new = get_genai_variable(admin, "var1");
	ok(var1_new == "test_value_123", 
	   "After SET, genai-var1 is 'test_value_123', got '%s'", var1_new.c_str());

	// Test 4: Set genai-var2 to a new integer value
	MYSQL_QUERY(admin, "SET genai-var2=42");
	std::string var2_new = get_genai_variable(admin, "var2");
	ok(var2_new == "42", 
	   "After SET, genai-var2 is '42', got '%s'", var2_new.c_str());

	// Test 5: Set genai-var1 with special characters
	MYSQL_QUERY(admin, "SET genai-var1='test with spaces'");
	std::string var1_special = get_genai_variable(admin, "var1");
	ok(var1_special == "test with spaces", 
	   "genai-var1 with spaces is 'test with spaces', got '%s'", var1_special.c_str());

	// Test 6: Verify SHOW VARIABLES LIKE pattern
	MYSQL_QUERY(admin, "SHOW VARIABLES LIKE 'genai-%'");
	MYSQL_RES* res = mysql_store_result(admin);
	int num_rows = mysql_num_rows(res);
	ok(num_rows == 2, 
	   "SHOW VARIABLES LIKE 'genai-%%' returns 2 rows, got %d", num_rows);
	mysql_free_result(res);

	// Test 7: Restore default values
	MYSQL_QUERY(admin, "SET genai-var1='default_value_1'");
	MYSQL_QUERY(admin, "SET genai-var2=100");
	ok(1,  "Restored default values for genai-var1 and genai-var2");

	return test_num;
}

/**
 * @brief Test variable persistence across storage layers
 *
 * Tests that variables are correctly copied between:
 * - Memory (main.global_variables)
 * - Disk (disk.global_variables)
 * - Runtime (GloGATH handler object)
 */
int test_variable_persistence(MYSQL* admin) {
	int test_num = 0;

	// Test 1: Set values and save to disk
	diag("Testing variable persistence: Set values, save to disk, modify, load from disk");
	MYSQL_QUERY(admin, "SET genai-var1='disk_test_value'");
	MYSQL_QUERY(admin, "SET genai-var2=999");
	MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO DISK");
	ok(1,  "Set genai-var1='disk_test_value', genai-var2=999 and saved to disk");

	// Test 2: Modify values in memory
	MYSQL_QUERY(admin, "SET genai-var1='memory_value'");
	MYSQL_QUERY(admin, "SET genai-var2=111");
	std::string var1_mem = get_genai_variable(admin, "var1");
	std::string var2_mem = get_genai_variable(admin, "var2");
	ok(var1_mem == "memory_value" && var2_mem == "111", 
	   "Modified in memory: genai-var1='memory_value', genai-var2='111'");

	// Test 3: Load from disk and verify original values restored
	MYSQL_QUERY(admin, "LOAD GENAI VARIABLES FROM DISK");
	std::string var1_disk = get_genai_variable(admin, "var1");
	std::string var2_disk = get_genai_variable(admin, "var2");
	ok(var1_disk == "disk_test_value" && var2_disk == "999", 
	   "After LOAD FROM DISK: genai-var1='disk_test_value', genai-var2='999'");

	// Test 4: Save to memory and verify
	MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO MEMORY");
	ok(1,  "SAVE GENAI VARIABLES TO MEMORY executed");

	// Test 5: Load from memory
	MYSQL_QUERY(admin, "LOAD GENAI VARIABLES FROM MEMORY");
	ok(1,  "LOAD GENAI VARIABLES FROM MEMORY executed");

	// Test 6: Test SAVE from runtime
	MYSQL_QUERY(admin, "SAVE GENAI VARIABLES FROM RUNTIME");
	ok(1,  "SAVE GENAI VARIABLES FROM RUNTIME executed");

	// Test 7: Test LOAD to runtime
	MYSQL_QUERY(admin, "LOAD GENAI VARIABLES TO RUNTIME");
	ok(1,  "LOAD GENAI VARIABLES TO RUNTIME executed");

	// Test 8: Restore default values
	MYSQL_QUERY(admin, "SET genai-var1='default_value_1'");
	MYSQL_QUERY(admin, "SET genai-var2=100");
	MYSQL_QUERY(admin, "SAVE GENAI VARIABLES TO DISK");
	ok(1,  "Restored default values and saved to disk");

	return test_num;
}

/**
 * @brief Test CHECKSUM commands for GenAI variables
 *
 * Tests all CHECKSUM variants to ensure they work correctly.
 */
int test_checksum_commands(MYSQL* admin) {
	int test_num = 0;

	// Test 1: CHECKSUM DISK GENAI VARIABLES
	diag("Testing CHECKSUM commands for GenAI variables");
	int rc1 = mysql_query(admin, "CHECKSUM DISK GENAI VARIABLES");
	ok(rc1 == 0,  "CHECKSUM DISK GENAI VARIABLES");
	if (rc1 == 0) {
		MYSQL_RES* res = mysql_store_result(admin);
		int num_rows = mysql_num_rows(res);
		ok(num_rows == 1,  "CHECKSUM DISK GENAI VARIABLES returns 1 row");
		mysql_free_result(res);
	} else {
		skip(1, "Skipping row count check due to error");
	}

	// Test 2: CHECKSUM MEM GENAI VARIABLES
	int rc2 = mysql_query(admin, "CHECKSUM MEM GENAI VARIABLES");
	ok(rc2 == 0,  "CHECKSUM MEM GENAI VARIABLES");
	if (rc2 == 0) {
		MYSQL_RES* res = mysql_store_result(admin);
		int num_rows = mysql_num_rows(res);
		ok(num_rows == 1,  "CHECKSUM MEM GENAI VARIABLES returns 1 row");
		mysql_free_result(res);
	} else {
		skip(1, "Skipping row count check due to error");
	}

	// Test 3: CHECKSUM MEMORY GENAI VARIABLES (alias for MEM)
	int rc3 = mysql_query(admin, "CHECKSUM MEMORY GENAI VARIABLES");
	ok(rc3 == 0,  "CHECKSUM MEMORY GENAI VARIABLES");
	if (rc3 == 0) {
		MYSQL_RES* res = mysql_store_result(admin);
		int num_rows = mysql_num_rows(res);
		ok(num_rows == 1,  "CHECKSUM MEMORY GENAI VARIABLES returns 1 row");
		mysql_free_result(res);
	} else {
		skip(1, "Skipping row count check due to error");
	}

	// Test 4: CHECKSUM GENAI VARIABLES (defaults to DISK)
	int rc4 = mysql_query(admin, "CHECKSUM GENAI VARIABLES");
	ok(rc4 == 0,  "CHECKSUM GENAI VARIABLES");
	if (rc4 == 0) {
		MYSQL_RES* res = mysql_store_result(admin);
		int num_rows = mysql_num_rows(res);
		ok(num_rows == 1,  "CHECKSUM GENAI VARIABLES returns 1 row");
		mysql_free_result(res);
	} else {
		skip(1, "Skipping row count check due to error");
	}

	return test_num;
}

/**
 * @brief Main test function
 *
 * Orchestrates all GenAI module tests.
 */
int main() {
	CommandLine cl;

	if (cl.getEnv()) {
		diag("Failed to get the required environmental variables.");
		return EXIT_FAILURE;
	}

	// Initialize connection to admin interface
	MYSQL* admin = mysql_init(NULL);
	if (!admin) {
		fprintf(stderr, "File %s, line %d, Error: mysql_init failed\n", __FILE__, __LINE__);
		return EXIT_FAILURE;
	}

	if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password,
		NULL, cl.admin_port, NULL, 0)) {
		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
		return EXIT_FAILURE;
	}

	diag("Connected to ProxySQL admin interface at %s:%d", cl.host, cl.admin_port);

	// Build the list of LOAD/SAVE commands to test
	std::vector<std::string> queries;
	add_genai_load_save_commands(queries);

	// Each command test = 2 tests (execution + optional result check)
	// LOAD/SAVE commands: 14 commands
	// Variable access tests: 7 tests
	// Persistence tests: 8 tests
	// CHECKSUM tests: 8 tests (4 commands × 2)
	int num_load_save_tests = (int)queries.size() * 2; // Each command + result check
	int total_tests = num_load_save_tests + 7 + 8 + 8;

	plan(total_tests);

	int test_count = 0;

	// ============================================================================
	// Part 1: Test LOAD/SAVE commands
	// ============================================================================
	diag("=== Part 1: Testing LOAD/SAVE GENAI VARIABLES commands ===");
	for (const auto& query : queries) {
		MYSQL* admin_local = mysql_init(NULL);
		if (!admin_local) {
			diag("Failed to initialize MySQL connection");
			continue;
		}

		if (!mysql_real_connect(admin_local, cl.host, cl.admin_username, cl.admin_password,
			NULL, cl.admin_port, NULL, 0)) {
			diag("Failed to connect to admin interface");
			mysql_close(admin_local);
			continue;
		}

		int rc = run_q(admin_local, query.c_str());
		ok(rc == 0,  "Command executed successfully: %s", query.c_str());

		// For SELECT/SHOW/CHECKSUM style commands, verify result set
		if (strncasecmp(query.c_str(), "SELECT ", 7) == 0 ||
			strncasecmp(query.c_str(), "SHOW ", 5) == 0 ||
			strncasecmp(query.c_str(), "CHECKSUM ", 9) == 0) {
			MYSQL_RES* res = mysql_store_result(admin_local);
			unsigned long long num_rows = mysql_num_rows(res);
			ok(num_rows != 0,  "Command returned rows: %s", query.c_str());
			mysql_free_result(res);
		} else {
			// For non-query commands, just mark the test as passed
			ok(1,  "Command completed: %s", query.c_str());
		}

		mysql_close(admin_local);
	}

	// ============================================================================
	// Part 2: Test variable access (SET and SELECT)
	// ============================================================================
	diag("=== Part 2: Testing variable access (SET and SELECT) ===");
	test_count += test_variable_access(admin);

	// ============================================================================
	// Part 3: Test variable persistence across layers
	// ============================================================================
	diag("=== Part 3: Testing variable persistence across storage layers ===");
	test_count += test_variable_persistence(admin);

	// ============================================================================
	// Part 4: Test CHECKSUM commands
	// ============================================================================
	diag("=== Part 4: Testing CHECKSUM commands ===");
	test_count += test_checksum_commands(admin);

	// ============================================================================
	// Cleanup
	// ============================================================================
	mysql_close(admin);

	diag("=== All GenAI module tests completed ===");

	return exit_status();
}
