<?php
/**
 * Sitemap Cache
 *
 * Handles JSON and XML file storage, atomic rebuild rotation, and index tracking.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Sitemap;

use SEOvaultSEO\Core\Options;

/**
 * File cache for sitemap data
 */
class Cache {

	/**
	 * Cache directory path.
	 *
	 * @var string
	 */
	private static $directory = '';

	/**
	 * Unified index filename.
	 *
	 * @var string
	 */
	private static $index_file = 'unified_index.json';

	/**
	 * Lock transient name.
	 *
	 * @var string
	 */
	private static $lock_name = 'seovaultseo_sitemap_rebuild_lock';

	/**
	 * Initialize the cache directory.
	 */
	public static function init() {
		$upload_dir      = wp_upload_dir();
		$base            = wp_normalize_path( trailingslashit( $upload_dir['basedir'] ) . 'seovault-seo/' );
		$cache_base      = $base . 'cache/';
		self::$directory = $cache_base . 'sitemap/';

		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
		WP_Filesystem();

		if ( ! is_dir( self::$directory ) ) {
			wp_mkdir_p( self::$directory );
		}

		self::protect_directory( $base );
		self::protect_directory( $cache_base );
		self::protect_directory( self::$directory );
	}

	/**
	 * Get the cache directory.
	 *
	 * @return string
	 */
	public static function get_directory() {
		if ( '' === self::$directory ) {
			self::init();
		}
		return self::$directory;
	}

	/**
	 * Store data in the cache.
	 *
	 * @param string $filename Relative filename.
	 * @param string $data Data to store.
	 * @return bool
	 */
	public static function store( $filename, $data ) {
		$filename = Sanitizer::filename( $filename );
		if ( '' === $filename ) {
			return false;
		}

		$path = self::get_directory() . $filename;
		self::ensure_parent_directory( $path );

		global $wp_filesystem;
		if ( $wp_filesystem && $wp_filesystem->put_contents( $path, $data, FS_CHMOD_FILE ) ) {
			return true;
		}

		// Native PHP fallback.
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
		return false !== file_put_contents( $path, $data );
	}

	/**
	 * Get data from the cache.
	 *
	 * @param string $filename Relative filename.
	 * @return string|false
	 */
	public static function get( $filename ) {
		$filename = Sanitizer::filename( $filename );
		if ( '' === $filename ) {
			return false;
		}

		$path = self::get_directory() . $filename;
		if ( ! file_exists( $path ) ) {
			return false;
		}

		global $wp_filesystem;
		if ( ! $wp_filesystem ) {
			self::load_filesystem();
		}

		if ( ! $wp_filesystem ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Fallback only
			$contents = file_get_contents( $path );
			return false !== $contents ? $contents : false;
		}

		return $wp_filesystem->get_contents( $path );
	}

	/**
	 * Delete a file from the cache.
	 *
	 * @param string $filename Relative filename.
	 * @return bool
	 */
	public static function delete( $filename ) {
		$filename = Sanitizer::filename( $filename );
		if ( '' === $filename ) {
			return false;
		}

		$path = self::get_directory() . $filename;
		if ( ! file_exists( $path ) ) {
			return true;
		}

		global $wp_filesystem;
		if ( ! $wp_filesystem ) {
			self::load_filesystem();
		}

		if ( ! $wp_filesystem ) {
			return wp_delete_file( $path );
		}

		return $wp_filesystem->delete( $path );
	}

	/**
	 * Check if a file exists.
	 *
	 * @param string $filename Relative filename.
	 * @return bool
	 */
	public static function exists( $filename ) {
		$filename = Sanitizer::filename( $filename );
		if ( '' === $filename ) {
			return false;
		}
		return file_exists( self::get_directory() . $filename );
	}

	/**
	 * Clear all files in the cache directory.
	 *
	 * @return bool
	 */
	public static function clear_all() {
		global $wp_filesystem;
		if ( ! $wp_filesystem ) {
			self::load_filesystem();
		}

		$dir = self::get_directory();
		if ( ! is_dir( $dir ) ) {
			return true;
		}

		if ( ! $wp_filesystem ) {
			self::recursive_unlink( $dir );
			return true;
		}

		$files = $wp_filesystem->dirlist( $dir, true, false );
		if ( ! is_array( $files ) ) {
			return true;
		}

		foreach ( $files as $name => $info ) {
			$item_path = $dir . $name;
			if ( 'd' === $info['type'] ) {
				$wp_filesystem->delete( $item_path, true );
			} else {
				$wp_filesystem->delete( $item_path );
			}
		}

		return true;
	}

	/**
	 * Begin an atomic rebuild. Rotate live directory to backup.
	 *
	 * @param string $prefix Prefix for the rebuild.
	 * @return bool
	 */
	public static function begin_atomic_rebuild( $prefix ) {
		$prefix = Sanitizer::prefix( $prefix );
		if ( '' === $prefix ) {
			return false;
		}

		if ( self::is_rebuild_locked() ) {
			return false;
		}

		self::set_rebuild_lock();

		$dir        = self::get_directory();
		$live_dir   = $dir . $prefix . '/';
		$backup_dir = $dir . $prefix . '.old/';

		if ( is_dir( $backup_dir ) ) {
			self::recursive_delete( $backup_dir );
		}

		if ( is_dir( $live_dir ) ) {
			if ( ! self::move_path( $live_dir, $backup_dir ) ) {
				self::clear_rebuild_lock();
				return false;
			}
		}

		self::ensure_parent_directory( $live_dir . 'index.html' );
		return true;
	}

	/**
	 * Commit the atomic rebuild. Remove backup directory.
	 *
	 * @param string $prefix Prefix for the rebuild.
	 * @return bool
	 */
	public static function commit_atomic_rebuild( $prefix ) {
		$prefix = Sanitizer::prefix( $prefix );
		if ( '' === $prefix ) {
			return false;
		}

		$dir        = self::get_directory();
		$backup_dir = $dir . $prefix . '.old/';

		if ( is_dir( $backup_dir ) ) {
			self::recursive_delete( $backup_dir );
		}

		self::clear_rebuild_lock();
		Options::set( 'sitemap_last_generated', time() );
		return true;
	}

	/**
	 * Abort the atomic rebuild. Restore backup directory.
	 *
	 * @param string $prefix Prefix for the rebuild.
	 * @return bool
	 */
	public static function abort_atomic_rebuild( $prefix ) {
		$prefix = Sanitizer::prefix( $prefix );
		if ( '' === $prefix ) {
			return false;
		}

		$dir        = self::get_directory();
		$live_dir   = $dir . $prefix . '/';
		$backup_dir = $dir . $prefix . '.old/';

		if ( is_dir( $live_dir ) ) {
			self::recursive_delete( $live_dir );
		}

		if ( is_dir( $backup_dir ) ) {
			self::move_path( $backup_dir, $live_dir );
		}

		self::clear_rebuild_lock();
		return true;
	}

	/**
	 * Check if a rebuild backup exists.
	 *
	 * @param string $prefix Prefix.
	 * @return bool
	 */
	public static function has_rebuild_backup( $prefix ) {
		$prefix = Sanitizer::prefix( $prefix );
		if ( '' === $prefix ) {
			return false;
		}
		return is_dir( self::get_directory() . $prefix . '.old/' );
	}

	/**
	 * Read from a rebuild backup.
	 *
	 * @param string $filename Relative filename.
	 * @return string|false
	 */
	public static function read_rebuild_backup( $filename ) {
		$filename = Sanitizer::filename( $filename );
		if ( '' === $filename ) {
			return false;
		}
		$path = self::get_directory() . 'sitemap.old/' . $filename;
		if ( ! file_exists( $path ) ) {
			return false;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$contents = file_get_contents( $path );
		return false !== $contents ? $contents : false;
	}

	/**
	 * Add or update an entry in the unified index.
	 *
	 * @param string $url Sub-sitemap URL.
	 * @param string $lastmod Last modified date.
	 */
	public static function update_index( $url, $lastmod ) {
		$index         = self::read_index();
		$index[ $url ] = $lastmod;
		self::store( self::$index_file, wp_json_encode( $index, JSON_PRETTY_PRINT ) );
	}

	/**
	 * Read the unified index.
	 *
	 * @return array
	 */
	public static function read_index() {
		$contents = self::get( self::$index_file );
		if ( false === $contents ) {
			return array();
		}
		$decoded = json_decode( $contents, true );
		return is_array( $decoded ) ? $decoded : array();
	}

	/**
	 * Check if a rebuild lock is active.
	 *
	 * @return bool
	 */
	public static function is_rebuild_locked() {
		return (bool) get_transient( self::$lock_name );
	}

	/**
	 * Set the rebuild lock.
	 *
	 * @return bool
	 */
	public static function set_rebuild_lock() {
		return set_transient( self::$lock_name, time(), 5 * MINUTE_IN_SECONDS );
	}

	/**
	 * Clear the rebuild lock.
	 *
	 * @return bool
	 */
	public static function clear_rebuild_lock() {
		return delete_transient( self::$lock_name );
	}

	/**
	 * Ensure the parent directory exists for a path.
	 *
	 * @param string $path File path.
	 */
	private static function ensure_parent_directory( $path ) {
		$dir = dirname( $path );
		if ( ! is_dir( $dir ) ) {
			wp_mkdir_p( $dir );
		}
		self::protect_directory( $dir );
	}

	/**
	 * Drop index.php / .htaccess so uploads cache dirs are not listable or served.
	 *
	 * @param string $dir Directory path.
	 */
	private static function protect_directory( $dir ) {
		$dir = trailingslashit( wp_normalize_path( $dir ) );
		if ( '' === $dir || '/' === $dir || ! is_dir( $dir ) ) {
			return;
		}

		$index = $dir . 'index.php';
		if ( ! file_exists( $index ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Lightweight dir sentinel; WP_Filesystem may be unavailable early.
			@file_put_contents( $index, "<?php\n// Silence is golden.\n" );
		}

		$htaccess = $dir . '.htaccess';
		if ( ! file_exists( $htaccess ) ) {
			$rules = "# SEOvault AI — deny direct access\n"
				. "<IfModule mod_authz_core.c>\n"
				. "Require all denied\n"
				. "</IfModule>\n"
				. "<IfModule !mod_authz_core.c>\n"
				. "Deny from all\n"
				. "</IfModule>\n";
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Lightweight dir sentinel; WP_Filesystem may be unavailable early.
			@file_put_contents( $htaccess, $rules );
		}
	}

	/**
	 * Load the WordPress filesystem.
	 */
	private static function load_filesystem() {
		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
		WP_Filesystem();
	}

	/**
	 * Move a file or directory via WP_Filesystem when available.
	 *
	 * @param string $from Source path.
	 * @param string $to   Destination path.
	 * @return bool
	 */
	private static function move_path( $from, $to ) {
		global $wp_filesystem;
		if ( ! $wp_filesystem ) {
			self::load_filesystem();
		}

		if ( $wp_filesystem ) {
			return $wp_filesystem->move( $from, $to, true );
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- Fallback when WP_Filesystem is unavailable.
		return rename( $from, $to );
	}

	/**
	 * Recursively delete a directory.
	 *
	 * @param string $path Path.
	 */
	private static function recursive_delete( $path ) {
		global $wp_filesystem;
		if ( $wp_filesystem ) {
			$wp_filesystem->delete( $path, true );
			return;
		}
		self::recursive_unlink( $path );
	}

	/**
	 * Recursive unlink fallback.
	 *
	 * @param string $dir Directory.
	 */
	private static function recursive_unlink( $dir ) {
		if ( ! is_dir( $dir ) ) {
			return;
		}
		// phpcs:disable WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.WP.AlternativeFunctions.file_system_read_rmdir, WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Fallback when WP_Filesystem is unavailable.
		$items = new \FilesystemIterator( $dir, \FilesystemIterator::SKIP_DOTS );
		foreach ( $items as $item ) {
			if ( $item->isDir() ) {
				self::recursive_unlink( $item->getPathname() );
			} else {
				wp_delete_file( $item->getPathname() );
			}
		}
		rmdir( $dir );
		// phpcs:enable WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.WP.AlternativeFunctions.file_system_read_rmdir, WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
	}
}
