<?php
/**
 * Capa de base de datos compatible con $wpdb de WordPress.
 */

final class JB_Database {
	private static $instance = null;
	private $pdo;
	private $prefix;
	private $last_error = '';
	private $last_query = '';
	private $insert_id = 0;
	private $rows_affected = 0;
	private $charset_collate = '';

	public $prefix_prop;

	public function __construct( array $config ) {
		$this->prefix = (string) $config['prefix'];
		$this->prefix_prop = $this->prefix;
		$charset = $config['charset'] ?? 'utf8mb4';
		$this->charset_collate = "DEFAULT CHARACTER SET {$charset} COLLATE {$charset}_unicode_ci";

		$dsn = sprintf(
			'mysql:host=%s;port=%d;dbname=%s;charset=%s',
			$config['host'],
			(int) ( $config['port'] ?? 3306 ),
			$config['name'],
			$charset
		);

		$this->pdo = new PDO( $dsn, $config['user'], $config['password'], array(
			PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
			PDO::ATTR_EMULATE_PREPARES   => false,
		) );
	}

	public static function init( array $config ) {
		self::$instance = new self( $config );
		$GLOBALS['wpdb'] = self::$instance;
		return self::$instance;
	}

	public static function instance() {
		return self::$instance;
	}

	public function get_charset_collate() {
		return $this->charset_collate;
	}

	public function prepare( $query, ...$args ) {
		if ( empty( $args ) ) {
			return $query;
		}
		// Compatibilidad con $wpdb->prepare( $query, $arg1, $arg2 ).
		if ( 1 === count( $args ) && is_array( $args[0] ) ) {
			$args = $args[0];
		}
		$escaped = array();
		foreach ( $args as $arg ) {
			if ( is_int( $arg ) || is_float( $arg ) ) {
				$escaped[] = (string) $arg;
			} elseif ( is_null( $arg ) ) {
				$escaped[] = 'NULL';
			} else {
				$escaped[] = "'" . $this->_real_escape( (string) $arg ) . "'";
			}
		}
		$i = 0;
		return preg_replace_callback(
			'/%[dfs]/',
			function () use ( &$i, $escaped ) {
				return $escaped[ $i++ ] ?? "''";
			},
			$query
		);
	}

	public function query( $query ) {
		$this->last_query = $query;
		try {
			$stmt = $this->pdo->query( $query );
			$this->last_error = '';
			$this->rows_affected = $stmt ? $stmt->rowCount() : 0;
			return $stmt ? $stmt->rowCount() : false;
		} catch ( PDOException $e ) {
			$this->last_error = $e->getMessage();
			if ( JB_App::is_debug() ) {
				error_log( 'JB DB Error: ' . $e->getMessage() . ' | Query: ' . $query );
			}
			return false;
		}
	}

	public function get_var( $query = null, $x = 0, $y = 0 ) {
		if ( null === $query ) {
			return null;
		}
		$rows = $this->get_results( $query, ARRAY_N );
		if ( ! isset( $rows[ $y ][ $x ] ) ) {
			return null;
		}
		return $rows[ $y ][ $x ];
	}

	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
		$rows = $this->get_results( $query, $output );
		return $rows[ $y ] ?? null;
	}

	public function get_results( $query = null, $output = OBJECT ) {
		if ( null === $query ) {
			return array();
		}
		$this->last_query = $query;
		try {
			$stmt = $this->pdo->query( $query );
			$this->last_error = '';
			$rows = $stmt->fetchAll( PDO::FETCH_ASSOC );
			if ( ARRAY_A === $output ) {
				return $rows;
			}
			if ( ARRAY_N === $output ) {
				return array_map( 'array_values', $rows );
			}
			return array_map( function ( $row ) {
				return (object) $row;
			}, $rows );
		} catch ( PDOException $e ) {
			$this->last_error = $e->getMessage();
			return array();
		}
	}

	public function get_col( $query = null, $x = 0 ) {
		$rows = $this->get_results( $query, ARRAY_N );
		$col  = array();
		foreach ( $rows as $row ) {
			$col[] = $row[ $x ] ?? null;
		}
		return $col;
	}

	public function insert( $table, $data, $format = null ) {
		$fields = array_keys( $data );
		$placeholders = implode( ', ', array_fill( 0, count( $fields ), '?' ) );
		$sql = 'INSERT INTO `' . $this->_escape_identifier( $table ) . '` (`' . implode( '`, `', array_map( array( $this, '_escape_identifier' ), $fields ) ) . '`) VALUES (' . $placeholders . ')';
		$stmt = $this->pdo->prepare( $sql );
		$stmt->execute( array_values( $data ) );
		$this->insert_id = (int) $this->pdo->lastInsertId();
		$this->rows_affected = $stmt->rowCount();
		return $this->rows_affected;
	}

	public function update( $table, $data, $where, $format = null, $where_format = null ) {
		$sets = array();
		$values = array();
		foreach ( $data as $k => $v ) {
			$sets[] = '`' . $this->_escape_identifier( $k ) . '` = ?';
			$values[] = $v;
		}
		list( $where_sql, $where_values ) = $this->build_where( $where );
		$sql = 'UPDATE `' . $this->_escape_identifier( $table ) . '` SET ' . implode( ', ', $sets ) . ' WHERE ' . $where_sql;
		$stmt = $this->pdo->prepare( $sql );
		$stmt->execute( array_merge( $values, $where_values ) );
		$this->rows_affected = $stmt->rowCount();
		return $this->rows_affected;
	}

	public function delete( $table, $where, $where_format = null ) {
		list( $where_sql, $where_values ) = $this->build_where( $where );
		$sql = 'DELETE FROM `' . $this->_escape_identifier( $table ) . '` WHERE ' . $where_sql;
		$stmt = $this->pdo->prepare( $sql );
		$stmt->execute( $where_values );
		$this->rows_affected = $stmt->rowCount();
		return $this->rows_affected;
	}

	private function build_where( $where ) {
		$parts = array();
		$values = array();
		foreach ( $where as $k => $v ) {
			$parts[] = '`' . $this->_escape_identifier( $k ) . '` = ?';
			$values[] = $v;
		}
		return array( implode( ' AND ', $parts ), $values );
	}

	public function _real_escape( $string ) {
		return str_replace( array( '\\', "\0", "\n", "\r", "'", '"', "\x1a" ), array( '\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z' ), $string );
	}

	/**
	 * Escapa caracteres especiales de LIKE (compatible con $wpdb->esc_like).
	 *
	 * @param string $text Texto a escapar.
	 * @return string
	 */
	public function esc_like( $text ) {
		return addcslashes( (string) $text, '_%\\' );
	}

	private function _escape_identifier( $name ) {
		return str_replace( '`', '``', $name );
	}

	public function __get( $name ) {
		if ( 'prefix' === $name ) {
			return $this->prefix;
		}
		if ( 'insert_id' === $name ) {
			return $this->insert_id;
		}
		if ( 'last_error' === $name ) {
			return $this->last_error;
		}
		if ( 'last_query' === $name ) {
			return $this->last_query;
		}
		if ( 'rows_affected' === $name ) {
			return $this->rows_affected;
		}
		return null;
	}
}

if ( ! defined( 'OBJECT' ) ) {
	define( 'OBJECT', 'OBJECT' );
}
if ( ! defined( 'ARRAY_A' ) ) {
	define( 'ARRAY_A', 'ARRAY_A' );
}
if ( ! defined( 'ARRAY_N' ) ) {
	define( 'ARRAY_N', 'ARRAY_N' );
}
