<?php

namespace EDPW\Core\Models;
defined( 'ABSPATH' ) or die();

abstract class BaseModel {
	/**
	 * Table name.
	 * @var string
	 */
	const TABLE_NAME = '';

	/**
	 * Table primary key.
	 */
	const PRIMARY_KEY = '';

	/**
	 * Field list.
	 * @var array
	 */
	public static array $fields = [];

	/**
	 * Returns wpdb instance.
	 * @return object
	 */
	public static function db() {
		global $wpdb;

		return $wpdb;
	}

	/**
	 * Get current table name.
	 * @return string
	 */
	public static function getTableName(): string {
		return self::db()->prefix . static::TABLE_NAME;
	}

	/**
	 * Get database charset collate.
	 * @return string
	 */
	public static function getCharsetCollate(): string {
		return self::db()->get_charset_collate();
	}

	/**
	 * Create the table.
	 * @return mixed
	 */
	abstract static function create();

	/**
	 * Execute database query.
	 *
	 * @param string $query query string.
	 *
	 * @return array
	 */
	public static function executeQuery( string $query ): array {
		if ( ! function_exists( 'dbDelta' ) ) {
			require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
		}
		$search  = [ '{table}', '{charset_collate}' ];
		$replace = [ self::getTableName(), self::getCharsetCollate() ];

		return dbDelta( str_replace( $search, $replace, $query ) );
	}

	/**
	 * Inserting the row to table.
	 *
	 * @param array $data insert data.
	 *
	 * @return int
	 */
	public static function insert( array $data ): int {
		if ( empty( static::$fields ) ) {
			return 0;
		}

		/*$columns = implode('`,`', array_keys($this->fields));
		$values = implode(',', $this->fields);*/
		$actual_values = self::formatData( $data, 'insert' );
		$result        = self::db()->insert( self::getTableName(), $actual_values );
		//self::db()->prepare("INSERT INTO {$this->table} (`{$columns}`) VALUES ({$values});", $actual_values);
		//self::$db->query($query);
		return $result ? self::db()->insert_id : $result;
	}

	/**
	 * Update the table.
	 *
	 * @param array $data update data.
	 * @param array $where where condition.
	 *
	 * @return bool|false|int
	 */
	public static function update( array $data, array $where = [] ) {
		if ( empty( $data ) ) {
			return false;
		}

		return self::db()->update( self::getTableName(), $data, $where );
	}

	/**
	 * Update the table.
	 *
	 * @param array $where where condition field.
	 *
	 * @return bool|false|int
	 */
	public static function delete( array $where ) {
		return self::db()->delete( self::getTableName(), $where );
	}

	/**
	 * Retrieves one variable.
	 *
	 * @param string $query Query string.
	 * @param array $values Replace value.
	 *
	 * @return string|null
	 */
	public static function getVar( string $query, array $values = [] ) {
		return self::db()->get_var( self::prepareQuery( $query, $values ) );
	}

	/**
	 * Retrieves one row.
	 *
	 * @param string $query Query string.
	 * @param array $values Replace values.
	 * @param string $output Return format.
	 *
	 * @return array|object|null
	 */
	public static function getRow( string $query, array $values = [], string $output = OBJECT ) {
		return self::db()->get_row( self::prepareQuery( $query, $values ), $output );
	}


	/**
	 * Retrieves an entire SQL result set.
	 *
	 * @param string $query Query string.
	 * @param array $values Replace values.
	 * @param string $output Return format.
	 *
	 * @return array|null
	 */
	public static function getResults( string $query, array $values = [], string $output = OBJECT ) {
		return self::db()->get_results( self::prepareQuery( $query, $values ), $output );
	}

	/**
	 * Prepare query.
	 *
	 * @param string $query Query string.
	 * @param array $values Replace values.
	 *
	 * @return string
	 */
	protected static function prepareQuery( string $query, array $values = [] ): string {
		$query = str_replace( '{table}', self::getTableName(), $query );
		if ( ! empty( $values ) && is_array( $values ) ) {
			$query = self::db()->prepare( $query, $values );
		}

		return $query;
	}

	/**
	 * Format the data according to value.
	 *
	 * @param array $data need to format data.
	 * @param string $type type.
	 *
	 * @return array
	 */
	public static function formatData( array $data, string $type = 'update' ): array {
		if ( empty( static::$fields ) ) {
			return [];
		}
		$result = [];

		foreach ( static::$fields as $key => $value ) {
			if ( $type == 'update' && ! $data[ $key ] ) {
				continue;
			}
			$key = trim( $key );
			switch ( trim( $value ) ) {
				case '%d':
					$value = (int) ( $data[ $key ] ?? 0 );
					break;
				case '%f':
					$value = (float) ( $data[ $key ] ?? 0 );
					break;
				default:
					$value = $data[ $key ] ?? null;
					if ( is_array( $value ) || is_object( $value ) ) {
						$value = wp_json_encode( $value );
					}
					break;
			}

			$result[ $key ] = $value;
		}

		return $result;
	}

	/**
	 * @param $where
	 * @param $single
	 * @param $select
	 *
	 * @return array|object|void|null
	 */
	public static function getWhere( $where, $select = '*', $single = true ) {
		if ( is_array( $select ) || is_object( $select ) ) {
			$select = implode( ',', $select );
		}
		if ( empty( $select ) ) {
			$select = '*';
		}
		$table_name = self::getTableName();
		$query      = "SELECT $select FROM $table_name WHERE $where;";
		if ( $single ) {
			return self::db()->get_row( $query );
		} else {
			return self::db()->get_results( $query );
		}
	}

	/**
	 * @param $select
	 *
	 * @return array|object|void|null
	 */
	public static function getAll( $select = '*' ) {
		if ( is_array( $select ) || is_object( $select ) ) {
			$select = implode( ',', $select );
		}
		if ( empty( $select ) ) {
			$select = '*';
		}
		$table_name = self::getTableName();
		$query      = "SELECT $select FROM $table_name;";

		return self::db()->get_results( $query );
	}


	/**
	 * get the data from table
	 *
	 * @param $key
	 *
	 * @return array|object|void|null
	 */
	public static function getByKey( $key ) {
		$key         = sanitize_key( $key );
		$table_name  = self::getTableName();
		$primary_key = static::PRIMARY_KEY;
		$query       = self::db()->prepare( "SELECT * FROM $table_name WHERE $primary_key = %d;", array( $key ) );

		return self::db()->get_row( $query );
	}

}