<?php
/**
 * BitForgeNet Guest Tickets - Scheduled maintenance.
 *
 * @package BitForgeNet_Guest_Tickets
 * @since   1.0.0
 */

defined( 'ABSPATH' ) || exit;

// This file talks to the plugin's own tables. WordPress has no API for them --
// they are not posts and not meta -- so $wpdb is the only way in. The rows
// change on every booking and every scan, which makes an object cache wrong
// more often than it is right. Table names are built from $wpdb->prefix and
// never from a request, so interpolating one carries nothing to inject; every
// value goes through $wpdb->prepare(), and the few identifiers that cannot
// (ORDER BY columns) are matched against a whitelist first.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter


/**
 * Hourly and daily housekeeping.
 */
class BFNET_Cron {

	/**
	 * Hook up.
	 */
	public function __construct() {
		add_action( 'bfnet_cron_hourly', array( $this, 'hourly' ) );
		add_action( 'bfnet_cron_daily', array( $this, 'daily' ) );
	}

	/**
	 * Keep event statuses and counters honest.
	 *
	 * @return void
	 */
	public function hourly() {
		global $wpdb;

		$table = BFNET_Event::table();
		$now   = current_time( 'mysql' );

		// Close registration once the window has passed.
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table} SET status = 'closed', updated_at = %s
				 WHERE status = 'open' AND registration_closes_at IS NOT NULL
				 AND registration_closes_at != '0000-00-00 00:00:00' AND registration_closes_at < %s",
				$now,
				$now
			)
		); // phpcs:ignore WordPress.DB.PreparedSQL

		// Open registration when the scheduled moment arrives.
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table} SET status = 'open', updated_at = %s
				 WHERE status = 'published' AND registration_opens_at IS NOT NULL
				 AND registration_opens_at != '0000-00-00 00:00:00' AND registration_opens_at <= %s
				 AND (registration_closes_at IS NULL OR registration_closes_at = '0000-00-00 00:00:00' OR registration_closes_at > %s)",
				$now,
				$now,
				$now
			)
		); // phpcs:ignore WordPress.DB.PreparedSQL

		// Flag sold-out events and refresh counters.
		$events = BFNET_Event::query( array( 'statuses' => array( 'open', 'sold_out' ), 'limit' => 200 ) );
		foreach ( $events as $event ) {
			BFNET_Order::refresh_event_counters( (int) $event->id );

			$left = BFNET_Event::seats_left( $event );
			if ( null === $left ) {
				continue;
			}

			$target = ( $left <= 0 ) ? 'sold_out' : 'open';
			if ( $target !== $event->status ) {
				$wpdb->update( $table, array( 'status' => $target, 'updated_at' => $now ), array( 'id' => (int) $event->id ), array( '%s', '%s' ), array( '%d' ) );
			}
		}
	}

	/**
	 * Daily clean-up.
	 *
	 * @return void
	 */
	public function daily() {
		BFNET_Log::purge();
		$this->purge_temp_files();
		$this->archive_finished_events();
	}

	/**
	 * Delete stale files from the temporary folder.
	 *
	 * @return void
	 */
	private function purge_temp_files() {
		$storage = BFNET_Install::storage();
		$dir     = $storage['dir'] . '/tmp';

		if ( ! is_dir( $dir ) ) {
			return;
		}

		$cutoff = time() - DAY_IN_SECONDS;
		$files  = glob( $dir . '/*' );

		if ( ! is_array( $files ) ) {
			return;
		}

		foreach ( $files as $file ) {
			if ( is_file( $file ) && 'index.php' !== basename( $file ) && filemtime( $file ) < $cutoff ) {
				wp_delete_file( $file );
			}
		}
	}

	/**
	 * Archive events that finished more than a week ago.
	 *
	 * @return void
	 */
	private function archive_finished_events() {
		global $wpdb;

		$table  = BFNET_Event::table();
		$cutoff = gmdate( 'Y-m-d H:i:s', time() - ( 7 * DAY_IN_SECONDS ) );

		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table} SET status = 'archived', updated_at = %s
				 WHERE status IN ('open','closed','sold_out','published') AND starts_at < %s",
				current_time( 'mysql' ),
				$cutoff
			)
		); // phpcs:ignore WordPress.DB.PreparedSQL
	}
}
