<?php
/**
 * Caching layer: transients + filesystem image cache.
 *
 * @package GoValid_QR
 */

defined( 'ABSPATH' ) || exit;

class GoValid_Cache {

	/** @var self|null */
	private static $instance = null;

	/** @var string */
	private $image_cache_dir;

	/** @var string */
	private $image_cache_url;

	public static function get_instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		$upload_dir           = wp_upload_dir();
		$this->image_cache_dir = $upload_dir['basedir'] . '/govalid-qr-cache';
		$this->image_cache_url = $upload_dir['baseurl'] . '/govalid-qr-cache';
	}

	/**
	 * Register cron hook.
	 */
	public function register(): void {
		add_action( 'govalid_qr_cache_cleanup', array( $this, 'cleanup_expired_images' ) );
	}

	/**
	 * Get a cached value.
	 *
	 * @param string $key Cache key (without prefix).
	 * @return mixed|false Cached value or false.
	 */
	public function get( string $key ) {
		return get_transient( 'govalid_qr_' . $key );
	}

	/**
	 * Set a cached value.
	 *
	 * @param string $key        Cache key.
	 * @param mixed  $value      Data to cache.
	 * @param int    $expiration TTL in seconds.
	 */
	public function set( string $key, $value, int $expiration = 300 ): void {
		set_transient( 'govalid_qr_' . $key, $value, $expiration );
	}

	/**
	 * Delete a cached value.
	 *
	 * @param string $key Cache key.
	 */
	public function delete( string $key ): void {
		delete_transient( 'govalid_qr_' . $key );
	}

	/**
	 * Get a locally cached QR image URL. Downloads if not cached.
	 *
	 * @param string $uuid      QR code UUID.
	 * @param string $image_url Remote image URL.
	 * @return string Local URL or original URL on failure.
	 */
	public function get_cached_image( string $uuid, string $image_url ): string {
		$filename = sanitize_file_name( $uuid ) . '.png';
		$filepath = $this->image_cache_dir . '/' . $filename;
		$fileurl  = $this->image_cache_url . '/' . $filename;

		// Return cached version if fresh (< 24 hours).
		if ( file_exists( $filepath ) && ( time() - filemtime( $filepath ) ) < DAY_IN_SECONDS ) {
			return $fileurl;
		}

		// Download and cache.
		if ( ! file_exists( $this->image_cache_dir ) ) {
			wp_mkdir_p( $this->image_cache_dir );
		}

		$response = wp_remote_get( $image_url, array( 'timeout' => 15 ) );
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
			return $image_url;
		}

		$body = wp_remote_retrieve_body( $response );
		if ( empty( $body ) ) {
			return $image_url;
		}

		global $wp_filesystem;
		if ( empty( $wp_filesystem ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			WP_Filesystem();
		}

		$wp_filesystem->put_contents( $filepath, $body, FS_CHMOD_FILE );

		return $fileurl;
	}

	/**
	 * Flush transients matching a given prefix.
	 *
	 * @param string $prefix Prefix to match (e.g. 'link_list_').
	 */
	public function flush_prefix( string $prefix ): void {
		global $wpdb;

		$like = '_transient_govalid_qr_' . $wpdb->esc_like( $prefix ) . '%';
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
				$like,
				str_replace( '_transient_', '_transient_timeout_', $like )
			)
		);
	}

	/**
	 * Flush all GoValid transients and image cache.
	 */
	public function flush_all(): void {
		global $wpdb;

		// Delete all transients with our prefix.
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			"DELETE FROM {$wpdb->options}
			 WHERE option_name LIKE '_transient_govalid_qr_%'
			    OR option_name LIKE '_transient_timeout_govalid_qr_%'
			    OR option_name LIKE '_transient_govalid_oauth_%'
			    OR option_name LIKE '_transient_timeout_govalid_oauth_%'"
		);

		// Delete cached images.
		$this->delete_image_cache_dir();
	}

	/**
	 * Clean up expired image cache files (called via WP-Cron).
	 */
	public function cleanup_expired_images(): void {
		if ( ! is_dir( $this->image_cache_dir ) ) {
			return;
		}

		$files = glob( $this->image_cache_dir . '/*.png' );
		if ( empty( $files ) ) {
			return;
		}

		foreach ( $files as $file ) {
			if ( ( time() - filemtime( $file ) ) > DAY_IN_SECONDS ) {
				wp_delete_file( $file );
			}
		}
	}

	/**
	 * Delete the entire image cache directory.
	 */
	private function delete_image_cache_dir(): void {
		if ( ! is_dir( $this->image_cache_dir ) ) {
			return;
		}

		$files = glob( $this->image_cache_dir . '/*' );
		if ( ! empty( $files ) ) {
			foreach ( $files as $file ) {
				wp_delete_file( $file );
			}
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
		rmdir( $this->image_cache_dir );
	}
}
