<?php
/**
 * The DB class.
 *
 * @package HBFWC
 */

namespace HBFWC\Utils;

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

/**
 * Class HBFWC\Utils\DB.
 */
class DB {
	/**
	 * Unprefixed site-wide table name.
	 */
	const TABLE_NAME = 'hbfwc_availability';

	/**
	 * Side-wide table name.
	 *
	 * @var string
	 */
	public $table;

	/**
	 * Class constructor.
	 */
	public function __construct() {
		$this->set_table_vars();
	}

	/**
	 * Register the plugin table names with WordPress.
	 *
	 * @since 1.0.0
	 */
	public function set_table_vars() {
		global $wpdb;

		$this->table = $wpdb->prefix . self::TABLE_NAME;

		// Register the plugin table names with WordPress.
		$wpdb->hbfwc    = $this->table;
		$wpdb->tables[] = self::TABLE_NAME;
	}

	/**
	 * Return the appropriate plugin table name
	 *
	 * @return string The plugin table name
	 * @since 1.0.0
	 */
	public function get_table_name(): string {
		return $this->table;
	}

	/**
	 * Determine whether a database table exists.
	 *
	 * @param string $table_name The table name.
	 * @param bool   $refresh Rerun the query, instead of using a cached value.
	 *
	 * @return bool Whether the database table exists.
	 */
	public static function table_exists( string $table_name, bool $refresh = false ): bool {
		global $wpdb;
		static $checked = array();

		if ( $refresh || ! isset( $checked[ $table_name ] ) ) {
			$result                 = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$checked[ $table_name ] = $result === $table_name;
		}

		return $checked[ $table_name ];
	}

	/**
	 * Create the plugin tables, or upgrade them if they already exist.
	 */
	public function create_or_upgrade_tables() {
		$this->create_table( $this->table );
	}

	/**
	 * Create a plugin table if it does not already exist.
	 *
	 * @param string $table_name Name of database table.
	 */
	public static function create_missing_table( string $table_name ) {
		if ( ! self::table_exists( $table_name ) ) {
			self::create_table( $table_name );
		}
	}

	/**
	 * Create a single db table.
	 *
	 * @param string $table_name The name of the table to create.
	 *
	 * @return bool Whether the table creation was successful.
	 * @since  1.0.0
	 * @uses   dbDelta() to apply the SQL code.
	 */
	public static function create_table( string $table_name ): bool {
		global $wpdb;
		$charset_collate = $wpdb->get_charset_collate();

		/* Create the database table */
		$sql = "CREATE TABLE $table_name (
					id           bigint(20)    NOT NULL AUTO_INCREMENT,
					post_id      bigint(20)    NULL,
					booking_date date          NULL,
					qty          INT           NULL,
					status       tinyint(1)    NOT NULL DEFAULT '1',
					PRIMARY KEY (id)
				) $charset_collate;";

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );

		$success = empty( $wpdb->last_error );

		if ( $success ) {
			do_action( 'hbfwc_internal_create_table', $table_name );
		}

		return $success;
	}

	/**
	 * Save new data.
	 *
	 * @param array $data The fields.
	 * @param array $format The format fields.
	 *
	 * @return mixed
	 */
	public function add_new( array $data, array $format ) {
		global $wpdb;

		$table  = $this->table;
		$result = $wpdb->insert( $table, $data, $format ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( false === $result ) {
			return null;
		}

		return $wpdb->insert_id;
	}

	/**
	 * Edit data.
	 *
	 * @param int   $db_id The DB id.
	 * @param array $data The data.
	 * @param array $data_format The format data.
	 *
	 * @return mixed
	 */
	public function update( int $db_id, array $data, array $data_format = array() ) {
		global $wpdb;

		$table = $this->table;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->update(
			$table,
			$data,
			array( 'id' => $db_id ),
			$data_format,
			array( '%d' )
		);

		if ( false === $result ) {
			return null;
		}

		return $db_id;
	}

	/**
	 * Edit data.
	 *
	 * @param array $data The data.
	 * @param array $where Where consdition.
	 * @param array $data_format The format data.
	 * @param array $where_format The format data.
	 *
	 * @return mixed
	 */
	public function update_custom( array $data, array $where, array $data_format, array $where_format ) {
		global $wpdb;

		$table = $this->table;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->update(
			$table,
			$data,
			$where,
			$data_format,
			$where_format
		);

		if ( false === $result ) {
			return null;
		}

		return true;
	}

	/**
	 * Delete a data.
	 *
	 * @param int $db_id The DB id.
	 *
	 * @return bool
	 */
	public function delete_entry( int $db_id ): bool {
		global $wpdb;

		$table = $this->table;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$remove = $wpdb->delete(
			$table,
			array(
				'id' => $db_id,
			),
			array(
				'%d',
			)
		);

		return ( ! $remove ) ? false : true;
	}

	/**
	 * Delete a data.
	 *
	 * @param array $where Where condition.
	 * @param array $format Data format.
	 *
	 * @return bool
	 */
	public function delete_custom_entry( array $where, array $format ): bool {
		global $wpdb;

		$table = $this->table;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$remove = $wpdb->delete(
			$table,
			$where,
			$format
		);

		return ( ! $remove ) ? false : true;
	}

	/**
	 * Delete all entries before today.
	 *
	 * @return mixed
	 */
	public function delete_entries_before_today() {
		global $wpdb;

		$date_now = wp_date( 'Y-m-d' );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$remove = $wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->prefix}hbfwc_availability WHERE `booking_date` < %s",
				$date_now
			)
		);

		return $remove;
	}

	/**
	 * Get a single entry by post ID and date
	 *
	 * @param int    $post_id The product ID.
	 * @param string $date Date.
	 * @param string $output The return data OBJECT|ARRAY_A|ARRAY_N.
	 *
	 * @return mixed.
	 */
	public function get_single_date_entry( int $post_id, string $date, string $output = 'OBJECT' ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$data = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}hbfwc_availability WHERE `post_id` = %d AND `booking_date` = %s",
				$post_id,
				$date
			),
			$output
		);

		return $data;
	}

	/**
	 * Get entries by post ID and date range
	 *
	 * @param int    $post_id The product ID.
	 * @param string $date_start Date start.
	 * @param string $date_end Date end.
	 * @param string $output The return data OBJECT|ARRAY_A|ARRAY_N.
	 *
	 * @return mixed.
	 */
	public function get_date_range_entries( int $post_id, string $date_start, string $date_end, string $output = 'OBJECT' ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}hbfwc_availability WHERE `post_id` = %d AND `booking_date` BETWEEN %s AND %s",
				$post_id,
				$date_start,
				$date_end,
			),
			$output
		);

		return $data;
	}


	/**
	 * Get entries date range
	 *
	 * @param string $date_start Date start.
	 * @param string $date_end Date end.
	 * @param string $output The return data OBJECT|ARRAY_A|ARRAY_N.
	 *
	 * @return mixed.
	 */
	public function get_date_range_all_entries( string $date_start, string $date_end, string $output = 'OBJECT' ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$data = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}hbfwc_availability WHERE `booking_date` BETWEEN %s AND %s",
				$date_start,
				$date_end,
			),
			$output
		);

		return $data;
	}

	/**
	 * Get entries by month
	 *
	 * @param int    $post_id The product ID.
	 * @param string $month Month to check.
	 * @param string $year Year to check.
	 * @param string $output The return data OBJECT|ARRAY_A|ARRAY_N.
	 *
	 * @return mixed.
	 */
	public function get_entries_by_month( int $post_id, string $month, string $year, string $output = 'OBJECT' ) {
		global $wpdb;

		$count_days = self::count_days_in_month( $year, $month );

		if ( ! $count_days ) {
			return false;
		}

		$start_date = $year . '-' . $month . '-01';
		$end_date   = $year . '-' . $month . '-' . $count_days;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}hbfwc_availability WHERE `post_id` = %d AND `booking_date` >= %s AND `booking_date` <= LAST_DAY(%s)",
				$post_id,
				$start_date,
				$end_date,
			),
			$output
		);

		return $results;
	}

	/**
	 * Count days in a month.
	 *
	 * @param string $year  Year to check.
	 * @param string $month Month to check.
	 *
	 * @since 1.0.0
	 */
	public static function count_days_in_month( string $year, string $month ) {
		if ( checkdate( $month, 1, $year ) ) {
			return cal_days_in_month( CAL_GREGORIAN, $month, $year );
		} else {
			return false; // Invalid month or year.
		}
	}
}
