<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MYAIO_Files_Backup {

	/**
	 * Optional helper: define a per-request time budget for step-based backups.
	 * This does NOT change PHP limits (Plugin Check friendly).
	 *
	 * @param int $seconds Suggested step budget.
	 * @return int
	 */
	private static function time_budget_seconds( $seconds = 20 ) {
		$seconds = (int) $seconds;

		if ( $seconds < 5 ) {
			$seconds = 5;
		} elseif ( $seconds > 30 ) {
			$seconds = 30;
		}

		return $seconds;
	}

	private static function dir_size_bytes( $dir ) {
		$size = 0;

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

		$it = new RecursiveIteratorIterator(
			new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ),
			RecursiveIteratorIterator::SELF_FIRST
		);

		foreach ( $it as $f ) {
			if ( $f->isFile() ) {
				$size += (int) $f->getSize();
			}
		}

		return $size;
	}

	private static function ensure_free_space_or_throw( $dest_dir, $needed_bytes ) {
		$free = disk_free_space( $dest_dir );

		// Can't determine; proceed without blocking.
		if ( false === $free ) {
			return;
		}

		$buffer = defined( 'MYAIO_DISK_SPACE_BUFFER' ) ? (int) MYAIO_DISK_SPACE_BUFFER : ( 50 * 1024 * 1024 );

		if ( $free < ( (int) $needed_bytes + $buffer ) ) {
			throw new Exception( 'Your server does not have enough free disk space to create the uploads backup.' );
		}
	}

	/**
	 * Delete all previous backup files in the destination directory.
	 *
	 * @param string $dest_dir Absolute directory path.
	 * @param string $exclude_file Optional filename to exclude from deletion.
	 */
	private static function cleanup_old_backups( $dest_dir, $exclude_file = '' ) {
		if ( ! is_dir( $dest_dir ) ) {
			return;
		}

		$pattern = trailingslashit( $dest_dir ) . '*.zip';
		$files   = glob( $pattern );

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

		foreach ( $files as $file ) {
			if ( '' !== $exclude_file && basename( $file ) === $exclude_file ) {
				continue;
			}

			// ✅ PCP-safe deletion (no is_writable(), no unlink()).
			if ( is_file( $file ) ) {
				wp_delete_file( $file );
			}
		}
	}

	/**
	 * Create a ZIP backup of wp-content/uploads (recommended default scope).
	 *
	 * @param string $dest_dir Absolute directory to save zip into.
	 * @param string $run_id   Run id used in filename.
	 * @return array { name, path, bytes }
	 * @throws Exception
	 */
	public static function run( $dest_dir, $run_id ) {
		if ( ! class_exists( 'ZipArchive' ) ) {
			throw new Exception( 'ZipArchive is not available on this server.' );
		}

		$dest_dir = (string) $dest_dir;
		$run_id   = (string) $run_id;

		if ( '' === $dest_dir ) {
			throw new Exception( 'Destination directory is empty.' );
		}

		if ( ! is_dir( $dest_dir ) && ! wp_mkdir_p( $dest_dir ) ) {
			throw new Exception( 'Cannot create files backup directory: ' . esc_html( $dest_dir ) );
		}

		// Clean up all previous backup files before creating new one.
		self::cleanup_old_backups( $dest_dir );
// Estimate required disk space based on full WordPress installation.
$src = untrailingslashit( ABSPATH );

$estimated = (int) ( self::dir_size_bytes( $src ) * 0.75 );
self::ensure_free_space_or_throw( $dest_dir, $estimated );

		$ts       = time();
		$safe_id  = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $run_id );
		$filename = 'full_site_' . $safe_id . '_' . $ts . '.zip';
		$path     = trailingslashit( $dest_dir ) . $filename;

		$zip = new ZipArchive();
		if ( true !== $zip->open( $path, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) {
			throw new Exception( 'Cannot create zip file: ' . esc_html( $path ) );
		}

		if ( '' === $src || ! is_dir( $src ) ) {
			$zip->close();
			throw new Exception( 'WordPress root directory not found: ' . esc_html( $src ) );
		}
		
		// Full WordPress backup.
		self::zip_dir( $zip, $src, '' );

		// Add a small manifest for debugging / restore context.
		$manifest = array(
			'site'          => home_url( '/' ),
			'createdAt'     => gmdate( 'c' ),
			'scope'         => 'full-wordpress',
			'wpVersion'     => get_bloginfo( 'version' ),
			'phpVersion'    => PHP_VERSION,
			'theme'         => wp_get_theme()->get_stylesheet(),
			'activePlugins' => array_values( (array) get_option( 'active_plugins', array() ) ),
			'root'          => $src,
		);

		$zip->addFromString( 'myaio_manifest.json', wp_json_encode( $manifest, JSON_PRETTY_PRINT ) );
		$zip->close();

		$bytes = 0;
		if ( file_exists( $path ) ) {
			$bytes = (int) filesize( $path );
		}

		return array(
			'name'  => $filename,
			'path'  => $path,
			'bytes' => $bytes,
		);
	}

	private static function zip_dir( ZipArchive $zip, $source_dir, $zip_root ) {
		$source_dir = rtrim( (string) $source_dir, '/\\' );
		$zip_root   = (string) $zip_root;

		$it = new RecursiveIteratorIterator(
			new RecursiveDirectoryIterator( $source_dir, FilesystemIterator::SKIP_DOTS ),
			RecursiveIteratorIterator::SELF_FIRST
		);

		foreach ( $it as $file ) {
			/** @var SplFileInfo $file */
			$full = $file->getPathname();

			// Skip unreadable.
			if ( ! is_readable( $full ) ) {
				continue;
			}

			// Skip folders containing "backup" (case-insensitive) in their path.
			$rel = ltrim( str_replace( $source_dir, '', $full ), '/\\' );
			if ( preg_match( '/backup/i', $rel ) ) {
				continue;
			}

			$zip_path = rtrim( $zip_root, '/\\' ) . '/' . str_replace( '\\', '/', $rel );

			if ( $file->isDir() ) {
				$zip->addEmptyDir( $zip_path );
			} else {
				$zip->addFile( $full, $zip_path );
			}
		}
	}
}