<?php
namespace WPO\IPS\Documents;

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

if ( ! class_exists( '\\WPO\\IPS\\Documents\\SequentialNumberStore' ) ) :

/**
 * Class handling database interaction for sequential numbers
 */

class SequentialNumberStore {

	private \wpdb  $wpdb;
	public string $store_name;
	public string $method;
	public string $table_name;
	public bool   $is_new = false;

	/**
	 * Constructor
	 *
	 * @param string $store_name
	 * @param string $method
	 */
	public function __construct( string $store_name, string $method = 'auto_increment' ) {
		global $wpdb;
		
		$this->wpdb       = $wpdb;
		$this->store_name = $store_name;
		$this->method     = $method;
		$this->table_name = apply_filters(
			'wpo_wcpdf_number_store_table_name',
			"{$this->wpdb->prefix}wcpdf_{$this->store_name}", // e.g. wp_wcpdf_invoice_number
			$this->store_name,
			$this->method
		);

		$this->init();
	}

	/**
	 * Initialize the number store (create table if it doesn't exist)
	 * 
	 * @return void
	 */
	public function init(): void {
		// check if table exists
		if ( ! $this->store_name_exists() ) {
			$this->is_new = true;
		} else {
			// Check calculated_number column if using 'calculate' method
			if ( 'calculate' === $this->method ) {
				$table_name = $this->table_name;

				// Check if the column exists
				$column_check_query = wpo_wcpdf_prepare_identifier_query(
					"SHOW COLUMNS FROM %i LIKE %s",
					array( $table_name ),
					array( 'calculated_number' )
				);

				$column_exists = $this->wpdb->get_var( $column_check_query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

				if ( empty( $column_exists ) ) {
					// Add calculated_number column
					$alter_query = wpo_wcpdf_prepare_identifier_query(
						"ALTER TABLE %i ADD `calculated_number` INT(16)",
						array( $table_name )
					);

					$this->wpdb->query( $alter_query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
				}
			}

			wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
			return; // no further business
		}

		// create table (in case of concurrent requests, this does no harm if it already exists)
		$charset_collate = $this->wpdb->get_charset_collate();
		// dbDelta is a sensitive kid, so we omit indentation
$sql = "CREATE TABLE {$this->table_name} (
  id int(16) NOT NULL AUTO_INCREMENT,
  order_id int(16),
  date datetime DEFAULT '1000-01-01 00:00:00' NOT NULL,
  calculated_number int (16),
  PRIMARY KEY  (id),
  KEY order_id (order_id)
) $charset_collate;";

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

		// catch mysql errors
		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
	}

	/**
	 * Consume/create the next number and return it
	 * 
	 * @param  int         $order_id Order ID
	 * @param  string|null $date     Local date, formatted as Y-m-d H:i:s
	 * @return int                   Number that was consumed/created
	 */
	public function increment( int $order_id = 0, ?string $date = null ): int {
		$number = 0;
		
		if ( empty( $date ) ) {
			$date = get_date_from_gmt( gmdate( 'Y-m-d H:i:s' ) );
		}

		do_action( 'wpo_wcpdf_before_sequential_number_increment', $this, $order_id, $date );

		$data = array(
			'order_id' => absint( $order_id ),
			'date'     => $date,
		);

		if ( 'auto_increment' === $this->method ) {
			$this->wpdb->insert( $this->table_name, $data );
			$number = $this->wpdb->insert_id;
			
		} elseif ( 'calculate' === $this->method ) {
			$number = $data['calculated_number'] = $this->get_next();
			$this->wpdb->insert( $this->table_name, $data );
		}

		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
		return $number;
	}
	
	/**
	 * Get the number that will be used on the next increment.
	 *
	 * @return int Next number
	 */
	public function get_next(): int {
		$table_name      = $this->table_name;
		$table_name_safe = wpo_wcpdf_sanitize_identifier( $table_name );
		$next            = 1;

		if ( 'auto_increment' === $this->method ) {
			// Clear cache on MySQL 8.0+ for accurate Auto_increment
			if ( $this->wpdb->get_var( "SHOW VARIABLES LIKE 'information_schema_stats_expiry'" ) ) {
				$this->wpdb->query( "SET SESSION information_schema_stats_expiry = 0" );
			}

			$query = $this->wpdb->prepare(
				"SHOW TABLE STATUS LIKE %s",
				$table_name_safe
			);

			// Get next auto_increment value
			$table_status = $this->wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

			$next = isset( $table_status->Auto_increment ) ? $table_status->Auto_increment : $next;

		} elseif ( 'calculate' === $this->method ) {
			$query = wpo_wcpdf_prepare_identifier_query(
				"SELECT * FROM %i WHERE id = ( SELECT MAX(id) FROM %i )",
				array( $table_name, $table_name )
			);

			$last_row = $this->wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

			if ( ! empty( $last_row->calculated_number ) ) {
				$next = (int) $last_row->calculated_number + 1;
			} elseif ( ! empty( $last_row->id ) ) {
				$next = (int) $last_row->id + 1;
			}
		}
		
		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
		return (int) $next;
	}
	
	/**
	 * Set the number that will be used on the next increment.
	 *
	 * @param int $number
	 * @return void
	 */
	public function set_next( int $number = 1 ): void {
		// Delete all rows
		$truncate_query = wpo_wcpdf_prepare_identifier_query(
			"TRUNCATE TABLE %i",
			array( $this->table_name )
		);
		$this->wpdb->query( $truncate_query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

		// Set AUTO_INCREMENT
		if ( $number > 1 ) {
			// if AUTO_INCREMENT is not 1, we need to make sure we have a 'highest value' in case of server restarts
			// https://serverfault.com/questions/228690/mysql-auto-increment-fields-resets-by-itself
			$highest_number = (int) $number - 1;

			$alter_query = wpo_wcpdf_prepare_identifier_query(
				"ALTER TABLE %i AUTO_INCREMENT = %d", 
				array( $this->table_name ), 
				array( $highest_number )
			);

			$this->wpdb->query( $alter_query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

			$data = array(
				'order_id' => 0,
				'date'     => get_date_from_gmt( gmdate( 'Y-m-d H:i:s' ) ),
			);

			if ( 'calculate' === $this->method ) {
				$data['calculated_number'] = $highest_number;
			}

			// After this insert, AUTO_INCREMENT will be equal to $number
			$this->wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$this->table_name,
				$data
			);
		} else {
			// Simple scenario, just set the AUTO_INCREMENT
			$alter_query = wpo_wcpdf_prepare_identifier_query(
				"ALTER TABLE %i AUTO_INCREMENT = %d", 
				array( $this->table_name ), 
				array( $number )
			);

			$this->wpdb->query( $alter_query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
		}
		
		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
	}
	
	/**
	 * Get the last date in the number store
	 *
	 * @param string $format
	 * @return string
	 */
	public function get_last_date( string $format = 'Y-m-d H:i:s' ): string {
		$table_name = $this->table_name;

		$query = wpo_wcpdf_prepare_identifier_query(
			"SELECT * FROM %i WHERE id = ( SELECT MAX(id) FROM %i )",
			array( $table_name, $table_name )
		);

		$row  = $this->wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
		$date = isset( $row->date ) ? $row->date : 'now';

		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
		return gmdate( $format, strtotime( $date ) );
	}
	
	/**
	 * Check if the number store table exists.
	 *
	 * @return bool
	 */
	public function store_name_exists(): bool {
		$table_name      = $this->table_name;
		$table_name_safe = wpo_wcpdf_sanitize_identifier( $table_name );
		
		$query  = $this->wpdb->prepare( "SHOW TABLES LIKE %s", $table_name_safe );
		$result = $this->wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared

		wcpdf_catch_db_object_errors( $this->wpdb, __METHOD__ );
		return $result === $table_name_safe;
	}

}

endif; // class_exists
