<?php
namespace QuantumCache\Support;

\defined( 'ABSPATH' ) || exit;

use QuantumCache\Store\MySQLStore;
use QuantumCache\Store\RedisStore;

/**
 * Maintenance ops (purge-all, etc.) with backend-specific implementations.
 */
final class Maintenance {

	/**
	 * Purge everything for the current site.
	 *
	 * @return array{backend:string, removed:int, note:string|null}
	 */
	public static function purge_all(): array {
		$store = StoreFactory::get();

		if ( $store instanceof MySQLStore ) {
			return self::purge_all_mysql();
		}
		if ( $store instanceof RedisStore ) {
			return self::purge_all_redis();
		}

		return array(
			'backend' => 'unknown',
			'removed' => 0,
			'note'    => 'Unsupported store for purge_all().',
		);
	}

	private static function purge_all_mysql(): array {
		global $wpdb;

				$entries_raw = (string) $wpdb->prefix . 'qc_entries';
				$tagmap_raw  = (string) $wpdb->prefix . 'qc_tagmap';

		// Validate table identifiers (defensive, mirrors MySQLStore constructor).
		foreach ( array( $entries_raw, $tagmap_raw ) as $tbl ) {
			if ( 1 !== preg_match( '/^[A-Za-z0-9_]+$/', $tbl ) ) {
				return array(
					'backend' => 'mysql',
					'removed' => 0,
					'note'    => 'Aborted: invalid table name.',
				);
			}
		}
				// esc_sql() is used here to satisfy tooling; identifiers are still validated above.
				$entries = esc_sql( $entries_raw );
				$tagmap  = esc_sql( $tagmap_raw );

		/**
		 * Cache maintenance routine:
		 * - Performs direct, prepared DB ops against QC-owned tables.
		 * - Not suitable for wp_cache_* (this *is* the backing store).
		 * - No external user input reaches these queries; table names are validated.
		 *
		 * @phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
		 * @phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
		 * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		 */

		// Count first (optional but useful for UX).
		$before_entries = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$entries}`" );
		$before_tags    = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$tagmap}`" );

		// Fast path: TRUNCATE (falls back to DELETE if privilege is missing).
		$ok1 = $wpdb->query( "TRUNCATE TABLE `{$entries}`" );
		if ( false === $ok1 ) {
			$wpdb->query( "DELETE FROM `{$entries}`" );
		}
		$ok2 = $wpdb->query( "TRUNCATE TABLE `{$tagmap}`" );
		if ( false === $ok2 ) {
			$wpdb->query( "DELETE FROM `{$tagmap}`" );
		}

	// @phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
	// @phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching
	// @phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		return array(
			'backend' => 'mysql',
			'removed' => $before_entries, // keys removed; tags are ancillary
			'note'    => 'Tags cleared: ' . $before_tags,
		);
	}


	private static function purge_all_redis(): array {
		if ( ! class_exists( 'Redis' ) ) {
			return array(
				'backend' => 'redis',
				'removed' => 0,
				'note'    => 'PHP Redis extension not available.',
			);
		}

		$host = defined( 'QUANTUMCACHE_REDIS_HOST' ) ? QUANTUMCACHE_REDIS_HOST : '127.0.0.1';
		$port = (int) ( defined( 'QUANTUMCACHE_REDIS_PORT' ) ? QUANTUMCACHE_REDIS_PORT : 6379 );
		$db   = (int) ( defined( 'QUANTUMCACHE_REDIS_DB' ) ? QUANTUMCACHE_REDIS_DB : 0 );

		$r = new \Redis();
		try {
			if ( ! $r->connect( $host, $port, 0.5 ) ) {
				throw new \RuntimeException( 'connect fail' );
			}
		} catch ( \Throwable $e ) {
			// Fallback to docker service name if localhost.
			if ( in_array( $host, array( '127.0.0.1', 'localhost' ), true ) ) {
				try {
					if ( ! $r->connect( 'redis', $port, 0.5 ) ) {
						throw new \RuntimeException( 'connect fail (redis)' );
					}
				} catch ( \Throwable $e2 ) {
					return array(
						'backend' => 'redis',
						'removed' => 0,
						'note'    => 'Redis not reachable.',
					);
				}
			} else {
				return array(
					'backend' => 'redis',
					'removed' => 0,
					'note'    => 'Redis not reachable.',
				);
			}
		}
		if ( $db ) {
			$r->select( $db );
		}

		$prefix = StoreFactory::sitePrefix(); // e.g. "site:0:"
		$match  = $prefix . '*';

		$it        = null;
		$removed   = 0;
		$batch     = array();
		$batch_max = 1000;
		$scans_cap = 1000; // hard safety cap

		for ( $i = 0; $i < $scans_cap; $i++ ) {
			$keys = $r->scan( $it, $match, 1000 );
			if ( false === $keys ) {
				break;
			}
			foreach ( $keys as $k ) {
				// Skip tag sets if you want to only count entries; but for a true purge we delete all keys.
				$batch[] = $k;
				if ( count( $batch ) >= $batch_max ) {
					$removed += (int) $r->del( $batch );
					$batch    = array();
				}
			}
			if ( 0 === $it ) {
				break;
			}
		}
		if ( ! empty( $batch ) ) {
			$removed += (int) $r->del( $batch );
		}

		return array(
			'backend' => 'redis',
			'removed' => $removed,
			'note'    => 'Keys deleted by prefix scan; safe and bounded.',
		);
	}
}
