<?php
namespace QuantumCache\Support;
\defined( 'ABSPATH' ) || exit;

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

final class StoreFactory {

	private static ?StoreInterface $inst  = null;
	private static string $active         = 'unknown';
	private static bool $purge_hook_bound = false;



	public static function sitePrefix(): string {
		// Allow a hard override, useful for migrations or non-standard setups.
		if ( defined( 'QUANTUMCACHE_KEY_PREFIX' ) && QUANTUMCACHE_KEY_PREFIX !== '' ) {
			// Ensure trailing colon for consistency if user forgot it.
			$pfx = rtrim( (string) QUANTUMCACHE_KEY_PREFIX, ':' ) . ':';
			/** @noinspection PhpUnhandledExceptionInspection */
			return apply_filters( 'quantumcache_key_prefix', $pfx );
		}

		// Determine blog id robustly. Some contexts can return 0 very early.
		$blog_id = function_exists( 'get_current_blog_id' ) ? (int) get_current_blog_id() : 0;
		if ( $blog_id <= 0 ) {
			// Fallbacks: main site for multisite, or 1 for single site.
			if ( function_exists( 'is_multisite' ) && is_multisite() && function_exists( 'get_main_site_id' ) ) {
				$blog_id = (int) get_main_site_id();
			} else {
				$blog_id = 1;
			}
		}

		// Optional salt to avoid cross-environment collisions when sharing a Redis.
		// Keep it short to avoid long keys; hash the salt if it's long.
		$salt = '';
		if ( defined( 'WP_CACHE_KEY_SALT' ) && WP_CACHE_KEY_SALT !== '' ) {
			// 6 chars from a stable hash so prefixes remain readable/short.
			$salt = substr( md5( (string) WP_CACHE_KEY_SALT ), 0, 6 ) . ':';
		}

		$prefix = 'qc:v1:' . $salt . 'site:' . $blog_id . ':'; // e.g., qc:v1:site:1:

		/** @noinspection PhpUnhandledExceptionInspection */
		return apply_filters( 'quantumcache_key_prefix', $prefix );
	}



	public static function make(): \QuantumCache\Store\StoreInterface {
		$mode = function_exists( 'get_option' ) ? (string) get_option( 'quantumcache_store_mode', 'auto' ) : 'auto';

		if ( 'redis' === $mode ) {
			self::apply_redis_options();
			return self::makeRedisOrThrow();
		}
		if ( 'mysql' === $mode ) {
			return self::makeMySQLOrThrow();
		}

		// AUTO: env overrides still honored
		$forced = null;
		if ( '1' === (string) getenv( 'QUANTUMCACHE_FORCE_MYSQL' ) ) {
			$forced = 'MYSQL';
		}
		if ( '1' === (string) getenv( 'QUANTUMCACHE_FORCE_REDIS' ) ) {
			$forced = 'REDIS';
		}
		$forced = apply_filters( 'quantumcache_store_guard_forced', $forced );

		if ( 'MYSQL' === $forced ) {
			return self::makeMySQLOrThrow();
		}
		if ( 'REDIS' === $forced ) {
			self::apply_redis_options();
			return self::makeRedisOrThrow();
		}

		self::apply_redis_options();
		$redis = self::tryRedisQuick();

		return $redis ?? self::makeMySQLOrThrow();
	}

	/**
		 * Construct a MySQL-backed store using the global $wpdb instance.
		 *
		 * @return StoreInterface
		 * @throws \RuntimeException If $wpdb is not available.
		 */
	private static function makeMySQLOrThrow(): StoreInterface {
		global $wpdb;

		if ( ! isset( $wpdb ) || ! $wpdb instanceof \wpdb ) {
			// In normal WordPress execution this should never happen; if it does, surface clearly.
			throw new \RuntimeException( 'QuantumCache MySQLStore requires a valid $wpdb instance.' );
		}

		return new MySQLStore( $wpdb );
	}

	private static function apply_redis_options(): void {
		if ( function_exists( 'get_option' ) ) {
			$h = (string) get_option( 'quantumcache_redis_host', '' );
			$p = (int) get_option( 'quantumcache_redis_port', 0 );
			$d = (int) get_option( 'quantumcache_redis_db', 0 );
			if ( $h && ! defined( 'QUANTUMCACHE_REDIS_HOST' ) ) {
				define( 'QUANTUMCACHE_REDIS_HOST', $h ); }
			if ( $p && ! defined( 'QUANTUMCACHE_REDIS_PORT' ) ) {
				define( 'QUANTUMCACHE_REDIS_PORT', $p ); }
			if ( $d && ! defined( 'QUANTUMCACHE_REDIS_DB' ) ) {
				define( 'QUANTUMCACHE_REDIS_DB', $d ); }
		}
	}


	private static function tryRedisQuick(): ?StoreInterface {
		try {
			// Your RedisStore doesn't take $opts; just instantiate and smoke-test cheaply.
			$store = self::makeRedisOrThrow();

			// Cheap liveness probe: SETEX a tiny key with 1s TTL and DEL it.
			// If Redis is unreachable, RedisStore::__construct() already disables itself internally.
			$tmp_key = 'qc:ping:' . bin2hex( random_bytes( 4 ) );
			$store->set( $tmp_key, '1', 1, null );
			$val = $store->get( $tmp_key );

			if ( null !== $val ) { // Yoda
				$store->delete( $tmp_key );
				return $store;
			}
		} catch ( \Throwable $e ) {
			// fall through to null
		}
		return null;
	}

	private static function makeRedisOrThrow( array $opts = array() ): StoreInterface {
		// $opts kept for API shape; RedisStore currently ignores it.
		return new RedisStore();
	}

	public static function get(): StoreInterface {
		if ( self::$inst instanceof StoreInterface ) {
			return self::$inst;
		}
		$store = self::make();

		// Infer active store name once.
		if ( $store instanceof \QuantumCache\Store\RedisStore ) {
			self::$active = 'redis';
		} else {
			self::$active = 'mysql';
		}

			self::$inst = $store;

		// Bind purge bridge exactly once, now that a store instance exists.
		if ( ! self::$purge_hook_bound ) {
			add_action(
				'quantumcache_purge_tags',
				static function ( array $tags ): void {
					try {
						// Always use the current instance in case it was reset.
						$s = \QuantumCache\Support\StoreFactory::get();
						if ( is_object( $s ) && method_exists( $s, 'purgeTags' ) ) {
							// Expectation: purgeTags accepts an array of strings.
							$s->purgeTags( $tags );
						}
					} catch ( \Throwable $e ) {
						// Optional: log with error_log if you want, but never fatal.
						// error_log( 'QuantumCache purge_tags error: ' . $e->getMessage() );
					}
				},
				10,
				1
			);
			self::$purge_hook_bound = true;
		}

		return self::$inst;
	}

	/** Optional but useful for headers/bench scripts */
	public static function storeName(): string {
		// Returns 'redis' or 'mysql' after first get().
		return self::$active;
	}

		// Inside class StoreFactory
	public static function reset(): void {
		self::$inst             = null;
		self::$active           = 'unknown';
		self::$purge_hook_bound = false;
	}
}
