<?php

/**

 * Database Security Helper Class

 *

 * Provides secure database operation wrappers to prevent SQL injection

 * and ensure GDPR compliance throughout the SensFRX plugin.

 *

 * @link       https://sensfrx.ai

 * @since      1.2.1

 *

 * @package    SensFRX

 * @subpackage SensFRX/includes

 */



/**

 * Database Security Helper Class

 *

 * This class provides secure wrappers around WordPress database operations

 * to ensure all queries use prepared statements and follow WordPress best practices.

 *

 * @since      1.2.1

 * @package    SensFRX

 * @subpackage SensFRX/includes

 */

class SensFRX_DB_Helper {



	/**

	 * List of valid table suffixes used by the plugin.

	 * This whitelist prevents SQL injection through table name manipulation.

	 *

	 * @since    1.2.1

	 * @access   private

	 * @var      array    $valid_tables    Array of valid table name suffixes.

	 */

	private static $valid_tables = array(

		'sensfrx_sleep',

		'sensfrx_webhook_logout',

		'sensfrx_api_active_and_webhook',

		'sensfrx_tab_data',

		'sensfrx_custome_filed',

		'sensfrx_real_activity',

		'sensfrx_shadow_activity',

		'sensfrx_email_templates',

		'sensfrx_pre_payment_order',

	);



	/**

	 * WordPress database instance.

	 *

	 * @since    1.2.1

	 * @access   private

	 * @var      wpdb    $wpdb    WordPress database instance.

	 */

	private static $wpdb = null;



	/**

	 * Initialize the database helper.

	 *

	 * Ensures the WordPress database instance is available for all operations.

	 * Uses lazy loading pattern to initialize only when needed.

	 *

	 * @since    1.2.1

	 * @access   private

	 */

	private static function init() {

		if ( null === self::$wpdb ) {

			global $wpdb;

			self::$wpdb = $wpdb;

		}

	}



	/**

	 * Get a safe table name with proper prefix.

	 *

	 * This method validates the table suffix against a whitelist and returns

	 * a properly prefixed table name safe for use in queries. This prevents

	 * SQL injection attacks through table name manipulation.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix The table name suffix (without prefix).

	 *                                 Must be one of the whitelisted table names.

	 *

	 * @return   string|false The full table name with prefix (e.g., 'wp_sensfrx_custome_filed')

	 *                        or false if the table suffix is not whitelisted.

	 *

	 * @example

	 *     $table = SensFRX_DB_Helper::get_table_name( 'sensfrx_custome_filed' );

	 *     // Returns: 'wp_sensfrx_custome_filed' (with your DB prefix)

	 */

	public static function get_table_name( $table_suffix ) {

		self::init();



		// Validate against whitelist to prevent arbitrary table access.

		// Using strict comparison (true) to ensure exact string match.

		if ( ! in_array( $table_suffix, self::$valid_tables, true ) ) {

			self::log_error( 'Invalid table suffix requested: ' . $table_suffix );

			return false;

		}



		// Return prefixed table name - safe for direct use in queries.

		return self::$wpdb->prefix . $table_suffix;

	}



	/**

	 * Safely execute a SELECT query with table name validation.

	 *

	 * This method provides a secure wrapper for SELECT queries with automatic

	 * table name validation, column sanitization, and prepared statement usage.

	 * All user input is properly escaped to prevent SQL injection.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 *                                  Must be from the whitelisted tables.

	 * @param    string $columns       Columns to select (default: '*').

	 *                                  Can be comma-separated list or SQL functions.

	 * @param    string $where         WHERE clause without 'WHERE' keyword (optional).

	 *                                  Use %s, %d, %f placeholders for values.

	 * @param    array  $where_values  Values for WHERE clause placeholders (optional).

	 *                                  Will be escaped via $wpdb->prepare().

	 * @param    string $order_by      ORDER BY clause without 'ORDER BY' keyword (optional).

	 *                                  Will be sanitized to prevent injection.

	 * @param    string $output_type   Output type (OBJECT, ARRAY_A, ARRAY_N).

	 *                                  Default: ARRAY_A (associative array).

	 *

	 * @return   array|null Query results as array of rows, or null on error.

	 *                      Empty array if no results found.

	 *

	 * @example

	 *     // Simple SELECT *

	 *     $results = SensFRX_DB_Helper::safe_select( 'sensfrx_custome_filed' );

	 *

	 *     // SELECT with specific columns and WHERE clause

	 *     $results = SensFRX_DB_Helper::safe_select(

	 *         'sensfrx_custome_filed',

	 *         'id, sensfrx_custom_field',

	 *         'id = %d',

	 *         array( 123 )

	 *     );

	 *

	 *     // SELECT with ORDER BY

	 *     $results = SensFRX_DB_Helper::safe_select(

	 *         'sensfrx_custome_filed',

	 *         '*',

	 *         '',

	 *         array(),

	 *         'id DESC'

	 *     );

	 */

	public static function safe_select( $table_suffix, $columns = '*', $where = '', $where_values = array(), $order_by = '', $output_type = ARRAY_A ) {

		self::init();



		// Step 1: Validate and get the full table name.

		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return null; // Invalid table - not in whitelist.

		}



		// Step 2: Sanitize column names to prevent SQL injection through column specification.

		$columns = self::sanitize_column_list( $columns );

		if ( false === $columns ) {

			return null; // Invalid column names detected.

		}



		// Step 3: Build the base SELECT query.

		$query = "SELECT {$columns} FROM {$table_name}";



		// Step 4: Add WHERE clause if provided.

		// Note: Values will be escaped via prepare() if $where_values is provided.

		if ( ! empty( $where ) ) {

			$query .= " WHERE {$where}";

		}



		// Step 5: Add ORDER BY clause if provided.

		if ( ! empty( $order_by ) ) {

			// Sanitize ORDER BY to prevent SQL injection through sorting.

			$order_by = self::sanitize_order_by( $order_by );

			if ( false !== $order_by ) {

				$query .= " ORDER BY {$order_by}";

			}

			// If sanitization fails, we continue without ORDER BY rather than failing the query.

		}



		// Step 6: Prepare query with values if provided (escapes all user input).

		if ( ! empty( $where_values ) ) {

			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Interpolated values ($table_name, $columns) are safe (whitelisted/sanitized). prepare() will escape $where_values via placeholders.

			$query = self::$wpdb->prepare( $query, ...$where_values );

		}

		// When $where_values is empty, query is safe: table name is whitelisted, columns are sanitized, no user input.



		// Step 7: Execute the query.

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared when $where_values is provided. When empty, query is safe (whitelisted table, sanitized columns, no user input).

		$results = self::$wpdb->get_results( $query, $output_type );



		// Step 8: Log any database errors (debug mode only).

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database query error: ' . self::$wpdb->last_error );

		}



		return $results;

	}



	/**

	 * Safely get a single row from a table.

	 *

	 * Wrapper for SELECT queries that return a single row. Uses prepared statements

	 * to prevent SQL injection.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 * @param    string $where         WHERE clause without 'WHERE' keyword.

	 *                                  Use %s, %d, %f placeholders.

	 * @param    array  $where_values  Values for WHERE clause placeholders.

	 * @param    string $output_type   Output type (OBJECT, ARRAY_A, ARRAY_N).

	 *

	 * @return   mixed|null Row object/array or null if not found or on error.

	 *

	 * @example

	 *     $row = SensFRX_DB_Helper::safe_get_row(

	 *         'sensfrx_custome_filed',

	 *         'id = %d',

	 *         array( 123 ),

	 *         ARRAY_A

	 *     );

	 */

	public static function safe_get_row( $table_suffix, $where = '', $where_values = array(), $output_type = ARRAY_A ) {

		self::init();



		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return null;

		}



		// Build query

		$query = "SELECT * FROM {$table_name}";



		// Add WHERE clause if provided

		if ( ! empty( $where ) ) {

			$query .= " WHERE {$where}";

		}



		// Prepare query if we have WHERE values

		if ( ! empty( $where_values ) ) {

			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Interpolated value ($table_name) is safe (whitelisted). prepare() will escape $where_values via placeholders.

			$query = self::$wpdb->prepare( $query, ...$where_values );

		}

		// When $where_values is empty, query is safe: table name is whitelisted, no user input.



		// Execute query

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared when $where_values is provided. When empty, query is safe (whitelisted table, no user input).

		$result = self::$wpdb->get_row( $query, $output_type );



		// Log errors if any

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database query error: ' . self::$wpdb->last_error );

		}



		return $result;

	}



	/**

	 * Safely get a single variable from a table.

	 *

	 * Wrapper for SELECT queries that return a single value. Useful for COUNT,

	 * MAX, MIN queries or fetching a single column value.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 * @param    string $column        Column name to retrieve. Can be SQL function

	 *                                  like COUNT(*), MAX(column), etc.

	 * @param    string $where         WHERE clause without 'WHERE' keyword.

	 * @param    array  $where_values  Values for WHERE clause placeholders.

	 *

	 * @return   string|null Variable value or null if not found or on error.

	 *

	 * @example

	 *     // Get a specific value

	 *     $value = SensFRX_DB_Helper::safe_get_var(

	 *         'sensfrx_custome_filed',

	 *         'sensfrx_custom_field',

	 *         'id = %d',

	 *         array( 123 )

	 *     );

	 *

	 *     // Get a count

	 *     $count = SensFRX_DB_Helper::safe_get_var(

	 *         'sensfrx_webhook_logout',

	 *         'COUNT(*)',

	 *         'client_id = %d',

	 *         array( 456 )

	 *     );

	 */

	public static function safe_get_var( $table_suffix, $column, $where = '', $where_values = array() ) {

		self::init();



		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return null;

		}



		// Sanitize column name

		$column = self::sanitize_column_name( $column );

		if ( false === $column ) {

			return null;

		}



		// Build query

		$query = "SELECT {$column} FROM {$table_name}";



		// Add WHERE clause if provided

		if ( ! empty( $where ) ) {

			$query .= " WHERE {$where}";

		}



		// Prepare query if we have WHERE values

		if ( ! empty( $where_values ) ) {

			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Interpolated values ($table_name, $column) are safe (whitelisted/sanitized). prepare() will escape $where_values via placeholders.

			$query = self::$wpdb->prepare( $query, ...$where_values );

		}

		// When $where_values is empty, query is safe: table name is whitelisted, column is sanitized, no user input.



		// Execute query

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared when $where_values is provided. When empty, query is safe (whitelisted table, sanitized column, no user input).

		$result = self::$wpdb->get_var( $query );



		// Log errors if any

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database query error: ' . self::$wpdb->last_error );

		}



		return $result;

	}



	/**

	 * Safely insert data into a table.

	 *

	 * Wrapper around $wpdb->insert() which already uses prepared statements.

	 * Validates table name against whitelist before insertion.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix The table name suffix (without prefix).

	 * @param    array  $data         Data to insert (column => value pairs).

	 * @param    array  $format       Array of formats for data values (optional).

	 *                                 Use %s for strings, %d for integers, %f for floats.

	 *

	 * @return   int|false Number of rows affected (should be 1 on success)

	 *                     or false on error.

	 *

	 * @example

	 *     $result = SensFRX_DB_Helper::safe_insert(

	 *         'sensfrx_custome_filed',

	 *         array(

	 *             'sensfrx_custom_field' => 'test_value',

	 *             'created_at' => current_time( 'mysql' )

	 *         ),

	 *         array( '%s', '%s' )

	 *     );

	 *     if ( $result ) {

	 *         $insert_id = SensFRX_DB_Helper::get_insert_id();

	 *     }

	 */

	public static function safe_insert( $table_suffix, $data, $format = null ) {

		self::init();



		// Validate table name against whitelist.

		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return false; // Invalid table name.

		}



		// $wpdb->insert already uses prepared statements - we just add table validation.

		$result = self::$wpdb->insert( $table_name, $data, $format );



		// Log errors if any (debug mode only).

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database insert error: ' . self::$wpdb->last_error );

		}



		return $result;

	}



	/**

	 * Safely update data in a table.

	 *

	 * Wrapper around $wpdb->update() which already uses prepared statements.

	 * Validates table name against whitelist before update.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 * @param    array  $data          Data to update (column => value pairs).

	 * @param    array  $where         WHERE conditions (column => value pairs).

	 * @param    array  $format        Array of formats for data values (optional).

	 * @param    array  $where_format  Array of formats for WHERE values (optional).

	 *

	 * @return   int|false Number of rows affected or false on error.

	 *                     Returns 0 if no rows were updated (data unchanged).

	 *

	 * @example

	 *     $result = SensFRX_DB_Helper::safe_update(

	 *         'sensfrx_custome_filed',

	 *         array( 'sensfrx_custom_field' => 'new_value' ),

	 *         array( 'id' => 123 ),

	 *         array( '%s' ),

	 *         array( '%d' )

	 *     );

	 */

	public static function safe_update( $table_suffix, $data, $where, $format = null, $where_format = null ) {

		self::init();



		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return false;

		}



		// $wpdb->update already uses prepared statements

		$result = self::$wpdb->update( $table_name, $data, $where, $format, $where_format );



		// Log errors if any

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database update error: ' . self::$wpdb->last_error );

		}



		return $result;

	}



	/**

	 * Safely delete data from a table.

	 *

	 * Wrapper around $wpdb->delete() which already uses prepared statements.

	 * Validates table name against whitelist before deletion.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 * @param    array  $where         WHERE conditions (column => value pairs).

	 *                                  IMPORTANT: Be specific to avoid mass deletion.

	 * @param    array  $where_format  Array of formats for WHERE values (optional).

	 *

	 * @return   int|false Number of rows deleted or false on error.

	 *

	 * @example

	 *     $deleted = SensFRX_DB_Helper::safe_delete(

	 *         'sensfrx_webhook_logout',

	 *         array( 'client_id' => 123 ),

	 *         array( '%d' )

	 *     );

	 */

	public static function safe_delete( $table_suffix, $where, $where_format = null ) {

		self::init();



		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return false;

		}



		// $wpdb->delete already uses prepared statements

		$result = self::$wpdb->delete( $table_name, $where, $where_format );



		// Log errors if any

		if ( self::$wpdb->last_error ) {

			self::log_error( 'Database delete error: ' . self::$wpdb->last_error );

		}



		return $result;

	}



	/**

	 * Check if a table exists in the database.

	 *

	 * Useful for checking if plugin tables have been created before

	 * attempting database operations.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    string $table_suffix The table name suffix (without prefix).

	 *

	 * @return   bool True if table exists, false otherwise or if invalid table name.

	 *

	 * @example

	 *     if ( SensFRX_DB_Helper::table_exists( 'sensfrx_custome_filed' ) ) {

	 *         // Table exists, safe to query

	 *     }

	 */

	public static function table_exists( $table_suffix ) {

		self::init();



		$table_name = self::get_table_name( $table_suffix );

		if ( false === $table_name ) {

			return false;

		}



		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $table_name comes from get_table_name() which validates against whitelist and returns safe table name. Query is properly prepared with %s placeholder.

		$result = self::$wpdb->get_var( self::$wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ));





		return ( $result === $table_name );

	}



	/**

	 * Sanitize a column name to prevent SQL injection.

	 *

	 * Validates column names against a whitelist pattern that allows:

	 * - Simple column names (alphanumeric and underscores)

	 * - Asterisk (*)

	 * - Common SQL aggregate functions: COUNT, MAX, MIN, AVG, SUM

	 * - DISTINCT keyword

	 *

	 * @since    1.2.1

	 * @access   private

	 *

	 * @param    string $column Column name to sanitize.

	 *

	 * @return   string|false Sanitized column name or false if invalid/malicious.

	 */

	private static function sanitize_column_name( $column ) {

		// Whitelist pattern for allowed column name formats.

		// Supports: column_name, COUNT(*), COUNT(column), MAX(column), MIN(column),

		// AVG(column), SUM(column), DISTINCT column.

		if ( preg_match( '/^([a-zA-Z0-9_]+|\*|COUNT\(\*\)|COUNT\([a-zA-Z0-9_]+\)|MAX\([a-zA-Z0-9_]+\)|MIN\([a-zA-Z0-9_]+\)|AVG\([a-zA-Z0-9_]+\)|SUM\([a-zA-Z0-9_]+\)|DISTINCT\s+[a-zA-Z0-9_]+)$/i', $column ) ) {

			return $column;

		}

		

		// Log invalid attempts for security monitoring.

		self::log_error( 'Invalid column name: ' . $column );

		return false;

	}



	/**

	 * Sanitize a column list for SELECT queries.

	 *

	 * Validates comma-separated column lists against whitelist patterns.

	 * Supports simple columns, SQL functions (COUNT, MAX, MIN, AVG, SUM),

	 * DISTINCT keyword, and AS aliases.

	 *

	 * @since    1.2.1

	 * @access   private

	 *

	 * @param    string $columns Column list to sanitize (e.g., 'id, name' or 'COUNT(*) AS total').

	 *

	 * @return   string|false Sanitized column list or false if any column is invalid.

	 */

	private static function sanitize_column_list( $columns ) {

		// If it's just *, allow it immediately.

		if ( '*' === $columns ) {

			return $columns;

		}



		// Split by comma and validate each column individually.

		$column_array = array_map( 'trim', explode( ',', $columns ) );

		$sanitized = array();



		foreach ( $column_array as $col ) {

			// Build whitelist pattern for allowed column formats.

			// Allows:

			// - Simple column names: column_name

			// - Column names with AS alias: column_name AS alias

			// - SQL functions: COUNT(*), COUNT(column), MAX(column), MIN(column), AVG(column), SUM(column)

			// - SQL functions with alias: COUNT(*) AS total

			// - DISTINCT: DISTINCT column_name

			$pattern = '/^(';

			$pattern .= '[a-zA-Z0-9_]+|'; // Simple column name.

			$pattern .= '\*|'; // Asterisk.

			$pattern .= 'COUNT\(\*\)|COUNT\([a-zA-Z0-9_]+\)|'; // COUNT functions.

			$pattern .= 'MAX\([a-zA-Z0-9_]+\)|MIN\([a-zA-Z0-9_]+\)|'; // MAX/MIN functions.

			$pattern .= 'AVG\([a-zA-Z0-9_]+\)|SUM\([a-zA-Z0-9_]+\)|'; // AVG/SUM functions.

			$pattern .= 'DISTINCT\s+[a-zA-Z0-9_]+'; // DISTINCT.

			$pattern .= ')(\s+AS\s+[a-zA-Z0-9_]+)?$/i'; // Optional AS alias.



			if ( preg_match( $pattern, $col ) ) {

				$sanitized[] = $col;

			} else {

				// Log invalid column for security monitoring.

				self::log_error( 'Invalid column in list: ' . $col );

				return false; // Reject entire list if any column is invalid.

			}

		}



		// Rejoin validated columns with proper spacing.

		return implode( ', ', $sanitized );

	}



	/**

	 * Sanitize ORDER BY clause to prevent SQL injection.

	 *

	 * Validates ORDER BY clauses against a whitelist pattern that only allows

	 * simple column names with optional ASC/DESC sorting direction.

	 * Prevents complex injection attacks through sorting clauses.

	 *

	 * @since    1.2.1

	 * @access   private

	 *

	 * @param    string $order_by ORDER BY clause to sanitize (e.g., 'id DESC' or 'created_at').

	 *

	 * @return   string|false Sanitized ORDER BY clause or false if invalid/malicious.

	 */

	private static function sanitize_order_by( $order_by ) {

		// Whitelist pattern: column_name optionally followed by ASC or DESC.

		// Does NOT allow multiple columns, functions, or complex expressions.

		if ( preg_match( '/^[a-zA-Z0-9_]+(\s+(ASC|DESC))?$/i', $order_by ) ) {

			return $order_by;

		}



		// Log invalid ORDER BY attempts for security monitoring.

		self::log_error( 'Invalid ORDER BY clause: ' . $order_by );

		return false;

	}



	/**

	 * Log database errors for debugging.

	 *

	 * Logs errors only when WP_DEBUG is enabled to avoid exposing

	 * sensitive information in production environments.

	 * All log messages are prefixed with [SensFRX DB Helper] for easy filtering.

	 *

	 * @since    1.2.1

	 * @access   private

	 *

	 * @param    string $message Error message to log.

	 */

	private static function log_error( $message ) {

		// Only log in debug mode - never expose errors in production.

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {

			if ( function_exists( 'error_log' ) ) {

				// Prefix all messages for easy log filtering.

				error_log( '[SensFRX DB Helper] ' . $message );

			}

		}

	}



	/**

	 * Get the last database error message.

	 *

	 * Useful for debugging failed database operations.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @return   string Last database error message. Empty string if no error.

	 */

	public static function get_last_error() {

		self::init();

		return self::$wpdb->last_error;

	}



	/**

	 * Get the last insert ID.

	 *

	 * Returns the AUTO_INCREMENT ID of the last inserted row.

	 * Only valid after a successful INSERT operation.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @return   int Last insert ID. Returns 0 if no insert has been performed.

	 *

	 * @example

	 *     SensFRX_DB_Helper::safe_insert( 'sensfrx_custome_filed', $data );

	 *     $new_id = SensFRX_DB_Helper::get_insert_id();

	 */

	public static function get_insert_id() {

		self::init();

		return self::$wpdb->insert_id;

	}



	/**

	 * Get the number of rows affected by the last query.

	 *

	 * Useful for checking if UPDATE or DELETE operations actually modified data.

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @return   int Number of rows affected by the last query.

	 *

	 * @example

	 *     SensFRX_DB_Helper::safe_update( 'sensfrx_custome_filed', $data, $where );

	 *     $updated_count = SensFRX_DB_Helper::get_rows_affected();

	 */

	public static function get_rows_affected() {

		self::init();

		return self::$wpdb->rows_affected;

	}



	/**

	 * GDPR-compliant data deletion for user.

	 *

	 * Deletes all user data from SensFRX tables to comply with GDPR

	 * "Right to be Forgotten" requirements. This method should be called

	 * when a user requests data deletion or when their WordPress account is deleted.

	 *

	 * Tables affected:

	 * - sensfrx_webhook_logout (user sessions)

	 * - sensfrx_sleep (user activity tracking)

	 *

	 * @since    1.2.1

	 * @access   public

	 *

	 * @param    int $user_id WordPress user ID to delete data for.

	 *

	 * @return   bool True if all deletions succeeded, false if any deletion failed.

	 *

	 * @example

	 *     // Hook into WordPress user deletion

	 *     add_action( 'delete_user', 'my_delete_user_data' );

	 *     function my_delete_user_data( $user_id ) {

	 *         SensFRX_DB_Helper::gdpr_delete_user_data( $user_id );

	 *     }

	 */

	public static function gdpr_delete_user_data( $user_id ) {

		// Validate user ID - must be positive integer.

		if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {

			return false;

		}



		$user_id = intval( $user_id );

		$success = true;



		// Delete from webhook logout table (user session data).

		$result = self::safe_delete( 'sensfrx_webhook_logout', array( 'client_id' => $user_id ), array( '%d' ) );

		if ( false === $result ) {

			$success = false;

			self::log_error( "GDPR deletion failed for user {$user_id} in webhook_logout table" );

		}



		// Delete from sleep table (user activity tracking).

		$result = self::safe_delete( 'sensfrx_sleep', array( 'cid' => $user_id ), array( '%d' ) );

		if ( false === $result ) {

			$success = false;

			self::log_error( "GDPR deletion failed for user {$user_id} in sleep table" );

		}



		// Allow other parts of the plugin to hook into GDPR deletion.

		// Other tables or external services can be cleaned up via this action.

		do_action( 'sensfrx_gdpr_delete_user_data', $user_id );



		return $success;

	}



	/**

	 * Get current timestamp in GMT/UTC for database storage

	 *

	 * This ensures all timestamps are stored consistently in GMT/UTC regardless of

	 * WordPress timezone settings or MySQL server timezone configuration.

	 *

	 * @since    3.1.0

	 * @access   public

	 *

	 * @return   string MySQL datetime format 'Y-m-d H:i:s' in GMT/UTC timezone

	 *

	 * @example

	 *     $data = array(

	 *         'sensfrx_log_type' => 'User Login',

	 *         'sensfrx_log1' => 'User logged in successfully',

	 *         'created_at' => SensFRX_DB_Helper::get_gmt_timestamp()

	 *     );

	 *     SensFRX_DB_Helper::safe_insert('sensfrx_real_activity', $data);

	 */

	public static function get_gmt_timestamp() {

		// current_time() with true parameter returns GMT/UTC time

		// Without true, it returns time in WordPress timezone

		return current_time( 'mysql', true );

	}



	/**

	 * Safe insert with automatic GMT timestamp for created_at field

	 *

	 * Wrapper around safe_insert() that automatically adds a GMT timestamp

	 * to the created_at field if not already present in the data array.

	 *

	 * This ensures consistent GMT/UTC timestamps across all activity logs

	 * without requiring manual timestamp addition in every insert call.

	 *

	 * @since    3.1.0

	 * @access   public

	 *

	 * @param    string $table_suffix  The table name suffix (without prefix).

	 * @param    array  $data          Data to insert (column => value pairs).

	 * @param    array  $format        Array of formats for data values (optional).

	 *

	 * @return   int|false Insert ID on success, false on error.

	 *

	 * @example

	 *     // created_at will be added automatically in GMT

	 *     $data = array(

	 *         'sensfrx_log_type' => 'User Login',

	 *         'sensfrx_log1' => 'User logged in successfully'

	 *     );

	 *     $result = SensFRX_DB_Helper::safe_insert_with_timestamp('sensfrx_real_activity', $data);

	 *

	 * @example

	 *     // Or specify custom timestamp (still in GMT)

	 *     $data = array(

	 *         'sensfrx_log_type' => 'User Login',

	 *         'sensfrx_log1' => 'User logged in successfully',

	 *         'created_at' => '2025-12-10 15:30:00' // Custom GMT timestamp

	 *     );

	 *     $result = SensFRX_DB_Helper::safe_insert_with_timestamp('sensfrx_real_activity', $data);

	 */

	public static function safe_insert_with_timestamp( $table_suffix, $data, $format = null ) {

		// Add GMT timestamp if created_at not already present

		if ( ! isset( $data['created_at'] ) ) {

			$data['created_at'] = self::get_gmt_timestamp();



			// Add format specifier for created_at if format array provided

			if ( is_array( $format ) ) {

				$format[] = '%s';

			}

		}



		// Use standard safe_insert with timestamp added

		return self::safe_insert( $table_suffix, $data, $format );

	}



	/**

	 * Insert an activity log entry with automatic GMT timestamp

	 *

	 * Simplified helper specifically for inserting activity logs into

	 * sensfrx_real_activity table. Automatically handles:

	 * - Table name prefixing

	 * - GMT/UTC timestamp for created_at

	 * - Consistent data format

	 *

	 * @since    3.1.0

	 * @access   public

	 *

	 * @param    string $log_type  The type of activity (e.g., 'Account Security', 'Transaction (Before Payment)')

	 * @param    string $log_message  The log message/description

	 *

	 * @return   int|false Insert ID on success, false on error.

	 *

	 * @example

	 *     // Simple one-liner for activity logs

	 *     SensFRX_DB_Helper::insert_activity_log(

	 *         'Account Security',

	 *         'Login | user@example.com | Approved | Risk: low'

	 *     );

	 *

	 * @example

	 *     // Transaction log

	 *     SensFRX_DB_Helper::insert_activity_log(

	 *         'Transaction (Before Payment)',

	 *         'Order #123 | user@example.com | Transaction approved | Risk: low'

	 *     );

	 */

	public static function insert_activity_log( $log_type, $log_message ) {

		$data = array(

			'sensfrx_log_type' => $log_type,

			'sensfrx_log1'     => $log_message,

			'created_at'       => self::get_gmt_timestamp()

		);



		$result = self::safe_insert( 'sensfrx_real_activity', $data, array( '%s', '%s', '%s' ) );

		

		if ( $result ) {

			return self::get_insert_id();

		}

		

		return false;

	}

}



