<?php
declare( strict_types=1 );

namespace FiboFilters\WP;

use FiboFilters\WPDBException;

// phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag,WordPress.DB.PreparedSQL.NotPrepared,WordPress.Security.EscapeOutput.ExceptionNotEscaped

/**
 * WPDB class
 *
 * This is a $wpdb wrapper with throwing exceptions when an error occurs. Unfortunately, phpcs and phpstan warnings had to be silenced.
 */
class WPDB {
	/** @var \wpdb */
	public $db;

	/** @var bool */
	private $in_transaction = false;

	/** @var bool */
	protected bool $throw_exception = false;

	/**
	 * @see \wpdb::delete()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function delete( $table, $where, $where_format = null ) {
		$this->prepare_instance();

		$result = $this->db->delete( $table, $where, $where_format );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			throw new WPDBException( $this->db->last_error );
		}

		return $result;
	}

	/**
	 * @see \wpdb::get_row()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
		$this->prepare_instance();

		$result = $this->db->get_row( $query, $output, $y );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::get_col()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function get_col( $query = null, $x = 0 ) {
		$this->prepare_instance();

		$result = $this->db->get_col( $query, $x );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::get_var()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function get_var( $query = null, $x = 0, $y = 0 ) {
		$this->prepare_instance();

		$result = $this->db->get_var( $query, $x, $y );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::get_results()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function get_results( $query = null, $output = OBJECT ) {
		$this->prepare_instance();

		$result = $this->db->get_results( $query, $output );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::insert()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function insert( $table, $data, $format = null ) {
		$this->prepare_instance();

		$result = $this->db->insert( $table, $data, $format );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::query()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function query( string $query ) {
		$this->prepare_instance();

		$result = $this->db->query( $query );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::update()
	 *
	 * @throws WPDBException DB exception.
	 * @phpstan-ignore-next-line
	 */
	public function update( $table, $data, $where, $format = null, $where_format = null ) {
		$this->prepare_instance();

		$result = $this->db->update( $table, $data, $where, $format, $where_format );
		if ( $this->throw_exception && ! empty( $this->db->last_error ) ) {
			// @phpstan-ignore-next-line
			throw new WPDBException( sprintf( 'Database error "%1$s" for query: "%2$s"', $this->db->last_error, trim( preg_replace( '/\s+/', ' ', $this->db->last_query ) ) ) );
		}

		return $result;
	}

	/**
	 * @see \wpdb::prepare()
	 *
	 * @phpstan-ignore-next-line
	 */
	public function prepare( $query, ...$args ) {
		$this->prepare_instance();

		return $this->db->prepare( $query, ...$args );
	}

	/**
	 * Start transaction
	 */
	public function start_transaction(): void {
		if ( ! $this->in_transaction ) {
			$this->prepare_instance();
			$this->db->query( 'START TRANSACTION' );
			$this->in_transaction = true;
		}
	}

	/**
	 * Commit transaction
	 */
	public function commit(): void {
		if ( $this->in_transaction ) {
			$this->prepare_instance();
			$this->db->query( 'COMMIT' );
			$this->in_transaction = false;
		}
	}

	/**
	 * Rollback transaction
	 */
	public function rollback(): void {
		if ( $this->in_transaction ) {
			$this->prepare_instance();
			$this->db->query( 'ROLLBACK' );
			$this->in_transaction = false;
		}
	}

	protected function prepare_instance(): void {
		global $wpdb;

		if ( ! isset( $this->db ) ) {
			$this->db = $wpdb;
		}
	}
}
