<?php
/**
 * MainWP Database Controller
 *
 * This file handles all interactions with the DB.
 *
 * @package MainWP/Dashboard
 */

namespace MainWP\Dashboard;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class MainWP_DB_Base
 *
 * @package MainWP\Dashboard
 */
class MainWP_DB_Base { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR.

    // phpcs:disable WordPress.DB.RestrictedFunctions, WordPress.DB.PreparedSQL.NotPrepared -- unprepared SQL ok, accessing the database directly to custom database functions.

    /**
     * Private static instance.
     *
     * @static
     * @var $instance  MainWP_DB_Base.
     */
    private static $instance = null;

    /**
     * Table prefix.
     *
     * @var string $table_prefix
     */
    protected $table_prefix;

    /**
     * WordPress Database.
     *
     * @var mixed $wpdb WordPress Database.
     */
    protected $wpdb;

    /**
     * MainWP_DB_Base constructor.
     *
     * Run each time the class is called.
     */
    public function __construct() {

        self::$instance = $this;

        /**
         * WordPress Database.
         *
         * @var mixed $wpdb Global WordPress Database.
         */
        global $wpdb;

        $this->wpdb         = &$wpdb;
        $this->table_prefix = $wpdb->prefix . 'mainwp_';
    }

    /**
     * Method test_connection()
     *
     * Test db connection.
     *
     * @uses \MainWP\Dashboard\MainWP_Logger::info()
     */
    protected function test_connection() {
        if ( ! static::ping( $this->wpdb->dbh ) ) {
            MainWP_Logger::instance()->info( esc_html__( 'Trying to reconnect WordPress database connection...', 'mainwp' ) );
            $this->wpdb->db_connect();
        }
    }

    /**
     * Method table_name()
     *
     * Create entire table name.
     *
     * @param mixed $suffix Table suffix.
     * @param null  $tablePrefix Table prefix.
     *
     * @return string Table name.
     */
    protected function table_name( $suffix, $tablePrefix = null ) {
        return ( null === $tablePrefix ? $this->table_prefix : $tablePrefix ) . $suffix;
    }


    /**
     * Method get_table_name()
     *
     * Create entire table name.
     *
     * @param mixed $suffix Table suffix.
     *
     * @return string Table name.
     */
    public function get_table_name( $suffix ) {
        return $this->table_name( $suffix );
    }

    /**
     * Method get_my_sql_version()
     *
     * Get MySQL Version.
     *
     * @return mixed MySQL vresion.
     */
    public function get_my_sql_version() {
        return $this->wpdb->get_var( 'SHOW VARIABLES LIKE "version"', 1 );
    }

    /**
     * Method get_var_field()
     *
     * Execute a SQL query and return a single variable.
     * This is a low-level wrapper for WPDB that expects SQL to be safely pre-constructed by callers.
     * Callers must ensure all dynamic values are either validated primitives or safely escaped.
     * See usage in get_sql_*() methods which construct safe SQL using whitelisted operations.
     *
     * @param string $sql Safe SQL query string. Must be pre-constructed safely by caller.
     *
     * @return mixed Database query result or null.
     */
    public function get_var_field( $sql ) {
        if ( null === $sql ) {
            return null;
        }
        // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is safe by contract; callers construct via get_sql_*() methods with validated/escaped values. All call sites verified in mainwp-db.php.
        return $this->wpdb->get_var( $sql );
    }

    /**
     * Method get_row_result()
     *
     * Execute a SQL query and return a single row result.
     * This is a low-level wrapper for WPDB that expects SQL to be safely pre-constructed by callers.
     * Callers must ensure all dynamic values are either validated primitives or safely escaped.
     * See usage in get_sql_*() methods which construct safe SQL using whitelisted operations.
     *
     * @param mixed $sql Safe SQL query string. Must be pre-constructed safely by caller.
     * @param int   $obj Return type: OBJECT or ARRAY_A (defaults to OBJECT).
     *
     * @return mixed Single row result or null.
     */
    public function get_row_result( $sql, $obj = OBJECT ) {
        if ( null === $sql ) {
            return null;
        }

        if ( ! in_array( $obj, array( OBJECT, ARRAY_A ) ) ) {
            $obj = OBJECT;
        }

        // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is safe by contract; callers construct via get_sql_*() methods with validated/escaped values. All call sites verified in mainwp-db.php.
        return $this->wpdb->get_row( $sql, $obj );
    }

    /**
     * Method get_results_result()
     *
     * Execute a SQL query and return multiple results indexed by primary key.
     * This is a low-level wrapper for WPDB that expects SQL to be safely pre-constructed by callers.
     * Callers must ensure all dynamic values are either validated primitives or safely escaped.
     * See usage in get_sql_*() methods which construct safe SQL using whitelisted operations.
     *
     * @param mixed $sql Safe SQL query string. Must be pre-constructed safely by caller.
     *
     * @return mixed Array of results indexed by primary key, or null.
     */
    public function get_results_result( $sql ) {
        if ( null === $sql ) {
            return null;
        }

        // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is safe by contract; callers construct via get_sql_*() methods with validated/escaped values. All call sites verified in mainwp-db.php.
        return $this->wpdb->get_results( $sql, OBJECT_K );
    }

    /**
     * Method query()
     *
     * SQL Query.
     *
     * @param mixed $sql SQL Query.
     *
     * @return mixed false|$result.
     */
    public function query( $sql ) {
        if ( null === $sql ) {
            return false;
        }

        $result = static::m_query( $sql, $this->wpdb->dbh ); // NOSONAR - required.

        if ( ! $result || ( empty( self::num_rows( $result ) ) ) ) { // NOSONAR - required.
            return false;
        }

        return $result;
    }


    /**
     * Method get_last_query()
     *
     * @return string Get last query.
     */
    public function get_last_query() {
        return $this->wpdb->last_query;
    }


    /**
     * Method escape_array()
     *
     * Escape SQL Data.
     *
     * @param mixed $data_arr Data array to escape.
     *
     * @return mixed Escapped SQL Data.
     */
    public function escape_array( $data_arr ) {
        if ( ! is_array( $data_arr ) || empty( $data_arr ) ) {
            return false;
        }
        $tmp_arr = array();
        foreach ( $data_arr as $dt ) {
            $tmp_arr[] = $this->escape( $dt );
        }
        return $tmp_arr;
    }

    /**
     * Method escape()
     *
     * Escape SQL Data.
     *
     * @param mixed $data Data to escape.
     *
     * @return mixed Escapped SQL Data.
     */
    public function escape( $data ) {
        if ( function_exists( 'esc_sql' ) ) {
            return esc_sql( $data );
        } else {
            return $this->wpdb->escape( $data );
        }
    }

    /**
     * Method use_mysqli()
     *
     * Use MySQLi, Support old & new versions of WordPress (3.9+).
     *
     * @return boolean|self false|$instance Instance of \mysqli
     */
    public static function use_mysqli() {
        if ( ! function_exists( '\mysqli_connect' ) ) {
            return false;
        }
        return self::$instance->wpdb->dbh instanceof \mysqli; // NOSONAR - required.
    }

    /**
     * Method ping()
     *
     * Ping MySQLi.
     *
     * @param mixed $link Query link.
     *
     * @return mixed \mysqli_ping
     */
    public static function ping( $link ) {
        if ( self::use_mysqli() ) { // NOSONAR - required.
            if ( version_compare( phpversion(), '8.4', '<' ) ) {
                return \mysqli_ping( $link );
            } else {
                try {
                    self::$instance->wpdb->query( 'DO 1' );
                    return true;
                } catch ( \mysqli_sql_exception $e ) { // NOSONAR - ok.
                    // error.
                    return false;
                }
            }
        } else {
            return \mysql_ping( $link );
        }
    }

    /**
     * Method m_query()
     *
     * MySQLi m_query.
     *
     * @param mixed $query Query params.
     * @param mixed $link Query link.
     *
     * @return mixed \mysqli_query
     */
    public static function m_query( $query, $link ) {
        if ( self::use_mysqli() ) { // NOSONAR - required.
            return \mysqli_query( $link, $query );
        } else {
            return \mysql_query( $query, $link );
        }
    }

    /**
     * Method fetch_object()
     *
     * Fetch object.
     *
     * @param mixed $result Query result.
     *
     * @return boolean|mixed false|\mysqli_fetch_object
     */
    public static function fetch_object( $result ) {
        if ( is_bool( $result ) ) {
            return $result;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            return \mysqli_fetch_object( $result );
        } else {
            return \mysql_fetch_object( $result );
        }
    }

    /**
     * Method free_result()
     *
     * MySQLi free result.
     *
     * @param mixed $result Query result.
     *
     * @return boolean|mixed false|\mysqli_free_result
     */
    public static function free_result( $result ) {
        if ( is_bool( $result ) ) {
            return $result;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            \mysqli_free_result( $result );
        } else {
            \mysql_free_result( $result );
        }
    }

    /**
     * Method data_seek()
     *
     * MySQLi data seek.
     *
     * @param mixed $result Query result.
     * @param mixed $offset Query offset.
     *
     * @return boolean|mixed false|\mysqli_data_seek
     */
    public static function data_seek( $result, $offset ) {
        if ( is_bool( $result ) ) {
            return $result;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            if ( ! ( $result instanceof \mysqli_result ) ) {
                return $result;
            }
            return \mysqli_data_seek( $result, $offset );
        } else {
            return \mysql_data_seek( $result, $offset );
        }
    }

    /**
     * Method fetch_array()
     *
     * MySQLi fetch array.
     *
     * @param mixed $result Query result.
     * @param null  $result_type Query result type.
     *
     * @return boolean|mixed false|\mysqli_fetch_array.
     */
    public static function fetch_array( $result, $result_type = null ) {
        if ( is_bool( $result ) ) {
            return $result;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            return \mysqli_fetch_array( $result, ( null === $result_type ? MYSQLI_BOTH : $result_type ) );
        } else {
            return \mysql_fetch_array( $result, ( null === $result_type ? MYSQL_BOTH : $result_type ) );
        }
    }


    /**
     * Method num_rows()
     *
     * MySQLi number of rows.
     *
     * @param mixed $result Query result.
     *
     * @return boolean|mixed false|\mysqli_num_rows.
     */
    public static function num_rows( $result ) {
        if ( ! self::is_result( $result ) ) { // NOSONAR - required.
            return false;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            return \mysqli_num_rows( $result );
        } else {
            return \mysql_num_rows( $result );
        }
    }

    /**
     * Method is_result()
     *
     * Return instance of \mysqli_result
     *
     * @param mixed $result Query result.
     *
     * @return boolean|mixed false|\mysqli_result
     */
    public static function is_result( $result ) {
        if ( is_bool( $result ) ) {
            return $result;
        }

        if ( self::use_mysqli() ) { // NOSONAR - required.
            return $result instanceof \mysqli_result;
        } else {
            return is_resource( $result );
        }
    }
    // phpcs:enable
}
