<?php
/**
 * Full plugin backup: export, restore, auto-backup rotation (max 5).
 *
 * @package SuiteMkt3k
 */

namespace SuiteMkt3k\Modules\Backup;

use SuiteMkt3k\Installer;
use SuiteMkt3k\Modules\Settings\Settings_Manager;
use SuiteMkt3k\Security\Capabilities;
use SuiteMkt3k\Security\Nonces;
use SuiteMkt3k\Utils\Logger;
use SuiteMkt3k\Utils\Sanitizer;

defined( 'ABSPATH' ) || exit;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching

final class Backup_Manager {

	public const MAX_STORED     = 5;
	public const FORMAT         = 'suitemkt3k-backup';
	public const CRON_HOOK      = 'suitemkt3k_auto_backup';
	private const FILE_PATTERN = 'suitemkt3k-backup-';

	public static function register(): void {
		add_action( 'admin_post_suitemkt3k_backup_create', array( __CLASS__, 'handle_create_download' ) );
		add_action( 'admin_post_suitemkt3k_backup_download_stored', array( __CLASS__, 'handle_download_stored' ) );
		add_action( 'admin_post_suitemkt3k_backup_restore_upload', array( __CLASS__, 'handle_restore_upload' ) );
		add_action( 'admin_post_suitemkt3k_backup_restore_stored', array( __CLASS__, 'handle_restore_stored' ) );
		add_action( self::CRON_HOOK, array( __CLASS__, 'maybe_run_auto_backup' ) );
		add_action( 'suitemkt3k_cron_daily', array( __CLASS__, 'maybe_run_auto_backup' ) );
	}

	/**
	 * @return list<string>
	 */
	public static function table_keys(): array {
		return array(
			'coupons',
			'coupon_designs',
			'subscribers',
			'newsletters',
			'newsletter_recipients',
			'newsletter_forms',
			'abandoned_carts',
			'cart_email_templates',
			'events',
			'stats_daily',
			'rate_limits',
			'logs',
		);
	}

	public static function backup_dir(): string {
		$uploads = wp_upload_dir();
		if ( ! empty( $uploads['error'] ) ) {
			return '';
		}
		return trailingslashit( $uploads['basedir'] ) . 'suitemkt3k/backups';
	}

	public static function ensure_backup_dir(): bool {
		$dir = self::backup_dir();
		if ( '' === $dir ) {
			return false;
		}
		if ( ! file_exists( $dir ) ) {
			wp_mkdir_p( $dir );
		}
		$index = $dir . '/index.php';
		if ( ! file_exists( $index ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			file_put_contents( $index, "<?php\n// Silence is golden.\n" );
		}
		$htaccess = $dir . '/.htaccess';
		if ( ! file_exists( $htaccess ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			file_put_contents(
				$htaccess,
				"# SuiteMkt3k backups — deny direct web access\nOptions -Indexes\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"
			);
		}
		if ( ! is_dir( $dir ) ) {
			return false;
		}
		global $wp_filesystem;
		if ( ! ( $wp_filesystem instanceof \WP_Filesystem_Base ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			WP_Filesystem();
		}
		return ( $wp_filesystem instanceof \WP_Filesystem_Base ) && $wp_filesystem->is_writable( $dir );
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function export_payload(): array {
		global $wpdb;

		$options = array(
			'suitemkt3k_settings',
			'suitemkt3k_db_version',
			'suitemkt3k_secret_key',
			'suitemkt3k_activated_at',
			'suitemkt3k_rewrite_version',
			'suitemkt3k_last_auto_backup',
		);
		$stored_options = array();
		foreach ( $options as $key ) {
			$stored_options[ $key ] = get_option( $key );
		}

		$tables = array();
		foreach ( self::table_keys() as $key ) {
			$table = Installer::table( $key );
			$rows = $wpdb->get_results(
				$wpdb->prepare( 'SELECT * FROM %i', $table ),
				ARRAY_A
			);
			$tables[ $key ] = is_array( $rows ) ? $rows : array();
		}

		return array(
			'format'     => self::FORMAT,
			'version'    => SUITEMKT3K_VERSION,
			'db_version' => get_option( 'suitemkt3k_db_version', SUITEMKT3K_DB_VERSION ),
			'created_at' => gmdate( 'c' ),
			'site_url'   => home_url(),
			'options'    => $stored_options,
			'tables'     => $tables,
		);
	}

	public static function create_stored_backup( string $source = 'manual' ): ?string {
		if ( ! self::ensure_backup_dir() ) {
			return null;
		}
		$payload = self::export_payload();
		$payload['source'] = $source;
		$json    = wp_json_encode( $payload );
		if ( ! is_string( $json ) || '' === $json ) {
			return null;
		}
		$filename = self::FILE_PATTERN . gmdate( 'Y-m-d-His' ) . '.json.gz';
		$path     = self::backup_dir() . '/' . $filename;
		$written  = file_put_contents( $path, gzencode( $json, 9 ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		if ( false === $written ) {
			return null;
		}
		self::rotate_stored_backups();
		Logger::info( 'backup', 'Backup stored', array( 'file' => $filename, 'source' => $source ) );
		return $filename;
	}

	public static function rotate_stored_backups(): void {
		$files = self::list_stored();
		if ( count( $files ) <= self::MAX_STORED ) {
			return;
		}
		$to_remove = array_slice( $files, self::MAX_STORED );
		foreach ( $to_remove as $file ) {
			$path = self::resolve_stored_path( $file['filename'] );
			if ( $path && is_file( $path ) ) {
				// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
				unlink( $path );
			}
		}
	}

	/**
	 * @return list<array{filename:string,size:int,created_at:string,source:string}>
	 */
	public static function list_stored(): array {
		$dir = self::backup_dir();
		if ( '' === $dir || ! is_dir( $dir ) ) {
			return array();
		}
		$files = glob( $dir . '/' . self::FILE_PATTERN . '*.json.gz' );
		if ( ! is_array( $files ) ) {
			return array();
		}
		usort(
			$files,
			static function ( $a, $b ) {
				return filemtime( $b ) <=> filemtime( $a );
			}
		);
		$out = array();
		foreach ( $files as $path ) {
			$name = basename( $path );
			$out[] = array(
				'filename'   => $name,
				'size'       => (int) filesize( $path ),
				'created_at' => gmdate( 'Y-m-d H:i:s', (int) filemtime( $path ) ),
				'source'     => self::read_backup_source( $path ),
			);
		}
		return $out;
	}

	private static function read_backup_source( string $path ): string {
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$raw = file_get_contents( $path );
		if ( false === $raw ) {
			return 'unknown';
		}
		$json = gzdecode( $raw );
		if ( false === $json ) {
			return 'unknown';
		}
		$data = json_decode( $json, true );
		return is_array( $data ) ? (string) ( $data['source'] ?? 'unknown' ) : 'unknown';
	}

	public static function resolve_stored_path( string $filename ): ?string {
		$filename = basename( $filename );
		if ( ! preg_match( '/^' . preg_quote( self::FILE_PATTERN, '/' ) . '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json\.gz$/', $filename ) ) {
			return null;
		}
		$path = self::backup_dir() . '/' . $filename;
		if ( ! is_file( $path ) ) {
			return null;
		}
		$real = realpath( $path );
		$dir  = realpath( self::backup_dir() );
		if ( ! $real || ! $dir || 0 !== strpos( $real, $dir ) ) {
			return null;
		}
		return $real;
	}

	/**
	 * Read a backup from disk. Accepts plain JSON or gzip (.json.gz).
	 * Uploads use a temp path without extension, so content / original name are used.
	 *
	 * @param string $path          Absolute path to the file (may be a PHP upload tmp name).
	 * @param string $original_name Optional client filename (e.g. suitemkt3k-backup-….json.gz).
	 * @return array<string, mixed>|null
	 */
	public static function read_file( string $path, string $original_name = '' ): ?array {
		if ( '' === $path || ! is_readable( $path ) ) {
			return null;
		}
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$raw = file_get_contents( $path );
		if ( false === $raw || '' === $raw ) {
			return null;
		}

		$looks_gzip = self::is_gzip_payload( $raw, $path, $original_name );
		if ( $looks_gzip ) {
			$decoded = gzdecode( $raw );
			if ( false === $decoded ) {
				return null;
			}
			$raw = $decoded;
		}

		$data = json_decode( $raw, true );
		if ( ! is_array( $data ) && ! $looks_gzip ) {
			// Fallback: some browsers rename .json.gz to .json but leave gzip bytes.
			$decoded = @gzdecode( $raw ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			if ( false !== $decoded ) {
				$data = json_decode( $decoded, true );
			}
		}
		if ( ! is_array( $data ) && defined( 'JSON_INVALID_UTF8_SUBSTITUTE' ) ) {
			$data = json_decode( $raw, true, 512, JSON_INVALID_UTF8_SUBSTITUTE );
		}

		return is_array( $data ) ? $data : null;
	}

	/**
	 * Detect gzip by magic bytes and/or filename (tmp uploads have no .gz suffix).
	 */
	private static function is_gzip_payload( string $raw, string $path, string $original_name = '' ): bool {
		if ( strlen( $raw ) >= 2 && "\x1f\x8b" === substr( $raw, 0, 2 ) ) {
			return true;
		}
		$name = $original_name !== '' ? $original_name : $path;
		return (bool) preg_match( '/\.(json\.gz|gz)$/i', $name );
	}

	/**
	 * Remember last restore failure detail for the admin UI (cleared on success).
	 */
	private static function set_restore_error( string $code, string $detail = '' ): void {
		set_transient(
			'suitemkt3k_restore_error',
			array(
				'code'   => sanitize_key( $code ),
				'detail' => sanitize_text_field( $detail ),
				'at'     => time(),
			),
			10 * MINUTE_IN_SECONDS
		);
	}

	/**
	 * @return array{code:string,detail:string}|null
	 */
	public static function consume_restore_error(): ?array {
		$err = get_transient( 'suitemkt3k_restore_error' );
		if ( ! is_array( $err ) ) {
			return null;
		}
		delete_transient( 'suitemkt3k_restore_error' );
		return array(
			'code'   => (string) ( $err['code'] ?? '' ),
			'detail' => (string) ( $err['detail'] ?? '' ),
		);
	}

	public static function restore_payload( array $payload ): bool {
		if ( ( $payload['format'] ?? '' ) !== self::FORMAT ) {
			self::set_restore_error( 'bad_format', (string) ( $payload['format'] ?? '' ) );
			return false;
		}
		if ( empty( $payload['tables'] ) || ! is_array( $payload['tables'] ) ) {
			self::set_restore_error( 'no_tables' );
			return false;
		}

		if ( function_exists( 'wp_raise_memory_limit' ) ) {
			wp_raise_memory_limit( 'admin' );
		}

		// Ensure schema exists before truncate/insert (safe, additive via dbDelta).
		Installer::install();

		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query( 'SET FOREIGN_KEY_CHECKS=0' );

		foreach ( self::table_keys() as $key ) {
			$table = Installer::table( $key );
			// Identifiers cannot use value placeholders; escape and allow only [A-Za-z0-9_].
			if ( ! preg_match( '/^[A-Za-z0-9_]+$/', $table ) ) {
				continue;
			}
			$safe_table = esc_sql( $table );
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- $safe_table is esc_sql()'d whitelist table name.
			$wpdb->query( "TRUNCATE TABLE `{$safe_table}`" );
		}

		foreach ( self::table_keys() as $key ) {
			if ( empty( $payload['tables'][ $key ] ) || ! is_array( $payload['tables'][ $key ] ) ) {
				continue;
			}
			$table = Installer::table( $key );
			foreach ( $payload['tables'][ $key ] as $row ) {
				if ( ! is_array( $row ) ) {
					continue;
				}
				self::insert_backup_row( $table, $row );
			}
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange
		$wpdb->query( 'SET FOREIGN_KEY_CHECKS=1' );

		if ( ! empty( $payload['options'] ) && is_array( $payload['options'] ) ) {
			foreach ( $payload['options'] as $option_key => $value ) {
				if ( ! is_string( $option_key ) || 0 !== strpos( $option_key, 'suitemkt3k_' ) ) {
					continue;
				}
				update_option( $option_key, $value, false );
			}
		}

		flush_rewrite_rules( false );
		delete_transient( 'suitemkt3k_restore_error' );
		Logger::info( 'backup', 'Backup restored', array( 'from' => $payload['created_at'] ?? '' ) );
		return true;
	}

	/**
	 * Insert one backup row, normalizing JSON nulls for $wpdb.
	 *
	 * @param array<string, mixed> $row
	 */
	private static function insert_backup_row( string $table, array $row ): void {
		global $wpdb;
		$data = array();
		foreach ( $row as $column => $value ) {
			if ( ! is_string( $column ) || '' === $column ) {
				continue;
			}
			// Skip non-scalar values (should not appear in SuiteMkt3k backups).
			if ( is_array( $value ) || is_object( $value ) ) {
				continue;
			}
			$data[ $column ] = $value;
		}
		if ( empty( $data ) ) {
			return;
		}
		$wpdb->insert( $table, $data );
	}

	public static function maybe_run_auto_backup(): void {
		$settings = (array) Settings_Manager::get( 'backup', '', array() );
		if ( empty( $settings['auto_enabled'] ) ) {
			return;
		}
		$interval = max( 1, min( 90, (int) ( $settings['interval_days'] ?? 7 ) ) );
		$last     = (int) get_option( 'suitemkt3k_last_auto_backup', 0 );
		if ( $last > 0 && ( time() - $last ) < ( $interval * DAY_IN_SECONDS ) ) {
			return;
		}
		$file = self::create_stored_backup( 'auto' );
		if ( $file ) {
			update_option( 'suitemkt3k_last_auto_backup', time(), false );
		}
	}

	public static function handle_create_download(): void {
		Capabilities::ensure( Capabilities::EDIT_SETTINGS );
		check_admin_referer( Nonces::ADMIN_ACTION, '_wpnonce' );

		$filename = self::create_stored_backup( 'manual' );
		if ( ! $filename ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'backup' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$path = self::resolve_stored_path( $filename );
		if ( ! $path ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'backup' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		self::stream_file( $path, $filename );
	}

	public static function handle_download_stored(): void {
		Capabilities::ensure( Capabilities::EDIT_SETTINGS );
		check_admin_referer( Nonces::ADMIN_ACTION, '_wpnonce' );

		$filename = sanitize_file_name( \SuiteMkt3k\Utils\Sanitizer::get( 'file', 'text' ) );
		$path     = self::resolve_stored_path( $filename );
		if ( ! $path ) {
			wp_die( esc_html__( 'Backup file not found.', 'suitemkt3k' ), 404 );
		}
		self::stream_file( $path, $filename );
	}

	private static function stream_file( string $path, string $filename ): void {
		if ( ob_get_level() ) {
			ob_end_clean();
		}
		nocache_headers();
		header( 'Content-Type: application/gzip' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		header( 'Content-Length: ' . (string) filesize( $path ) );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
		readfile( $path );
		exit;
	}

	public static function handle_restore_upload(): void {
		Capabilities::ensure( Capabilities::EDIT_SETTINGS );
		check_admin_referer( Nonces::ADMIN_ACTION, '_wpnonce' );

		$restore_confirm = \SuiteMkt3k\Utils\Sanitizer::post( 'restore_confirm', 'text' );
		if ( '' === $restore_confirm ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'confirm' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		/*
		 * Do NOT wp_unslash() tmp_name on Windows: stripslashes can corrupt paths
		 * (e.g. C:\tmp\... → C:<TAB>mp\...), which makes is_uploaded_file() fail
		 * even when UPLOAD_ERR_OK and the file exists.
		 */
		// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$upload_err = isset( $_FILES['backup_file']['error'] ) ? (int) $_FILES['backup_file']['error'] : UPLOAD_ERR_NO_FILE;
		$tmp_raw    = isset( $_FILES['backup_file']['tmp_name'] ) ? (string) $_FILES['backup_file']['tmp_name'] : '';
		$raw_name   = isset( $_FILES['backup_file']['name'] ) ? wp_unslash( (string) $_FILES['backup_file']['name'] ) : '';
		// phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		if ( UPLOAD_ERR_INI_SIZE === $upload_err || UPLOAD_ERR_FORM_SIZE === $upload_err ) {
			self::set_restore_error( 'too_large', (string) $upload_err );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'too_large' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$readable = ( '' !== $tmp_raw && is_readable( $tmp_raw ) );
		$is_upload = ( '' !== $tmp_raw && is_uploaded_file( $tmp_raw ) );
		if ( UPLOAD_ERR_OK !== $upload_err || ! $readable ) {
			self::set_restore_error(
				'upload',
				'error=' . $upload_err . ' readable=' . ( $readable ? '1' : '0' ) . ' is_uploaded=' . ( $is_upload ? '1' : '0' )
			);
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$name = sanitize_file_name( basename( str_replace( '\\', '/', $raw_name ) ) );
		if ( ! preg_match( '/\.(json\.gz|json|gz)$/i', $name ) ) {
			self::set_restore_error( 'invalid_name', $name );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'invalid' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		if ( ! self::ensure_backup_dir() ) {
			self::set_restore_error( 'backup_dir' );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		// Move out of PHP tmp into a stable path with a real extension (fixes Windows + gzip detection).
		// Do not call move_uploaded_file() — forbidden by Plugin Check; copy after validation is the approved path.
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$head  = (string) file_get_contents( $tmp_raw, false, null, 0, 2 );
		$is_gz = ( "\x1f\x8b" === $head ) || (bool) preg_match( '/\.(json\.gz|gz)$/i', $name );
		$ext   = $is_gz ? 'json.gz' : 'json';
		$dest  = trailingslashit( self::backup_dir() ) . 'restore-upload-' . gmdate( 'YmdHis' ) . '.' . $ext;

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_copy -- validated upload tmp; move_uploaded_file forbidden by WPCS Plugin Check.
		$moved_ok = copy( $tmp_raw, $dest );
		if ( $moved_ok && $is_upload ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
			@unlink( $tmp_raw );
		}
		if ( ! $moved_ok || ! is_readable( $dest ) ) {
			self::set_restore_error( 'move_failed', $dest );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$payload = self::read_file( $dest, $name );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
		@unlink( $dest );

		if ( ! $payload ) {
			self::set_restore_error( 'unreadable', $name );
			Logger::warn( 'backup', 'Restore upload: could not parse backup file', array( 'name' => $name ) );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}
		if ( ( $payload['format'] ?? '' ) !== self::FORMAT ) {
			self::set_restore_error( 'bad_format', (string) ( $payload['format'] ?? '' ) );
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		if ( ! self::restore_payload( $payload ) ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'ok' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
		exit;
	}

	public static function handle_restore_stored(): void {
		Capabilities::ensure( Capabilities::EDIT_SETTINGS );
		check_admin_referer( Nonces::ADMIN_ACTION, '_wpnonce' );

		$restore_confirm = \SuiteMkt3k\Utils\Sanitizer::post( 'restore_confirm', 'text' );
		if ( '' === $restore_confirm ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'confirm' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$filename = sanitize_file_name( \SuiteMkt3k\Utils\Sanitizer::post( 'file', 'text' ) );
		$path     = self::resolve_stored_path( $filename );
		if ( ! $path ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		$payload = self::read_file( $path );
		if ( ! $payload || ! self::restore_payload( $payload ) ) {
			wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'failed' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( array( 'tab' => 'backup', 'restore' => 'ok' ), admin_url( 'admin.php?page=suitemkt3k-settings' ) ) );
		exit;
	}

	public static function format_bytes( int $bytes ): string {
		if ( $bytes < 1024 ) {
			return $bytes . ' B';
		}
		if ( $bytes < 1048576 ) {
			return round( $bytes / 1024, 1 ) . ' KB';
		}
		return round( $bytes / 1048576, 2 ) . ' MB';
	}

	public static function stored_dir_label(): string {
		$dir = self::backup_dir();
		if ( '' === $dir ) {
			return '';
		}
		$uploads = wp_upload_dir();
		if ( ! empty( $uploads['basedir'] ) && str_starts_with( $dir, $uploads['basedir'] ) ) {
			return 'wp-content/uploads/suitemkt3k/backups/';
		}
		return $dir;
	}
}
