<?php
/**
 * `wp blaminhor backup ...` — Backup commands (read + delete only).
 *
 * Scope: list and delete existing backups. Creating a backup from the
 * CLI is intentionally not exposed in P3 — the admin UI runs it as a
 * stepped AJAX job to avoid PHP timeouts, and restoring is destructive
 * enough that it stays admin-only with the current safety net.
 *
 * @package BlaminhorEssentials
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
	return;
}

/**
 * Inspect and remove local Blaminhor Essentials backups.
 */
class Blaminhor_Essentials_CLI_Backup extends Blaminhor_Essentials_CLI_Module {

	/**
	 * @var string
	 */
	protected $module_id = 'backup';

	/**
	 * Path to the backups directory for the current site.
	 *
	 * @return string
	 */
	private function backup_dir() {
		$base = trailingslashit( WP_CONTENT_DIR ) . 'blaminhor-essentials/backups';
		if ( is_multisite() ) {
			$base .= '/' . (int) get_current_blog_id();
		}
		return $base;
	}

	/**
	 * List local backups grouped by prefix (each backup = 1+ ZIP files).
	 *
	 * ## OPTIONS
	 *
	 * [--fields=<fields>]
	 * : Columns. Available: prefix, components, archives, size, created_at.
	 *
	 * [--format=<format>]
	 * : Render format (table, json, csv, yaml, count, ids). Default: table.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor backup list
	 *     $ wp blaminhor backup list --format=ids
	 *
	 * @subcommand list
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function list_( $args, $assoc_args ) {
		$dir = $this->backup_dir();
		if ( ! is_dir( $dir ) ) {
			$this->format_for_output( array(), $assoc_args, array( 'prefix', 'components', 'archives', 'size', 'created_at' ) );
			return;
		}

		$files   = glob( trailingslashit( $dir ) . '*.zip' );
		$grouped = array();
		foreach ( (array) $files as $path ) {
			$name = basename( $path, '.zip' );
			// Filename format: {sitename}-YYYY-MM-DD-HHMMSS-{component}[-part{N}]
			if ( ! preg_match( '/^(.+-\d{4}-\d{2}-\d{2}-\d{6})-(.+?)(?:-part\d+)?$/', $name, $m ) ) {
				continue;
			}
			$prefix    = $m[1];
			$component = $m[2];
			if ( ! isset( $grouped[ $prefix ] ) ) {
				$grouped[ $prefix ] = array(
					'prefix'     => $prefix,
					'components' => array(),
					'archives'   => 0,
					'size'       => 0,
					'created_at' => (int) filemtime( $path ),
				);
			}
			$grouped[ $prefix ]['components'][ $component ] = true;
			$grouped[ $prefix ]['archives']++;
			$grouped[ $prefix ]['size'] += (int) filesize( $path );
		}

		krsort( $grouped );
		$rows = array();
		foreach ( $grouped as $g ) {
			$rows[] = array(
				'prefix'     => (string) $g['prefix'],
				'components' => implode( ',', array_keys( $g['components'] ) ),
				'archives'   => (string) (int) $g['archives'],
				'size'       => blaminhor_essentials_format_size( (int) $g['size'] ),
				'created_at' => date_i18n( 'Y-m-d H:i:s', (int) $g['created_at'] ),
			);
		}
		$this->format_for_output( $rows, $assoc_args, array( 'prefix', 'components', 'archives', 'size', 'created_at' ) );
	}

	/**
	 * Print summary info about the backup directory.
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args (unused).
	 * @param array $assoc_args Associative args.
	 */
	public function info( $args, $assoc_args ) {
		$dir   = $this->backup_dir();
		$files = is_dir( $dir ) ? (array) glob( trailingslashit( $dir ) . '*.zip' ) : array();
		$total = 0;
		foreach ( $files as $f ) {
			$total += (int) filesize( $f );
		}
		$module   = $this->get_module_instance();
		$settings = $module->get_effective_settings();
		$rows = array(
			array( 'key' => 'backup_dir',            'value' => $dir ),
			array( 'key' => 'total_size',            'value' => blaminhor_essentials_format_size( $total ) ),
			array( 'key' => 'archives_on_disk',      'value' => (string) count( $files ) ),
			array( 'key' => 'retention_count',       'value' => (string) (int) $settings['retention_count'] ),
			array( 'key' => 'schedule_enabled',      'value' => $settings['schedule_enabled'] ? 'true' : 'false' ),
			array( 'key' => 'schedule_db_frequency', 'value' => (string) $settings['schedule_db_frequency'] ),
			array( 'key' => 'auto_backup_on_update', 'value' => $settings['auto_backup_on_update'] ? 'true' : 'false' ),
		);
		$this->format_for_output( $rows, $assoc_args, array( 'key', 'value' ) );
	}

	/**
	 * Delete a backup (every ZIP + log file sharing its prefix).
	 *
	 * ## OPTIONS
	 *
	 * <prefix>
	 * : The backup prefix shown by `list` (e.g. mysite-2026-05-17-093015).
	 *
	 * [--yes]
	 * : Skip the interactive confirmation prompt.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp blaminhor backup delete mysite-2026-05-17-093015 --yes
	 *
	 * @when after_wp_load
	 *
	 * @param array $args       Positional args: [<prefix>].
	 * @param array $assoc_args Associative args.
	 */
	public function delete( $args, $assoc_args ) {
		$this->ensure_capabilities();
		if ( empty( $args[0] ) ) {
			WP_CLI::error( __( 'Missing <prefix> argument.', 'blaminhor-essentials' ) );
		}
		// Sanitise: only allow chars from the documented filename pattern.
		$prefix = preg_replace( '/[^A-Za-z0-9_.\-]/', '', (string) $args[0] );
		if ( '' === $prefix ) {
			WP_CLI::error( __( 'Invalid prefix after sanitisation.', 'blaminhor-essentials' ) );
		}

		$dir   = $this->backup_dir();
		$globs = array(
			trailingslashit( $dir ) . $prefix . '-*.zip',
			trailingslashit( $dir ) . $prefix . '.log',
			trailingslashit( $dir ) . $prefix . '-restore.log',
		);
		$found = array();
		foreach ( $globs as $g ) {
			foreach ( (array) glob( $g ) as $path ) {
				$found[] = $path;
			}
		}

		if ( empty( $found ) ) {
			WP_CLI::error( sprintf(
				/* translators: %s: prefix typed by the user */
				__( 'No archives found for prefix "%s".', 'blaminhor-essentials' ),
				$prefix
			) );
		}

		WP_CLI::confirm(
			sprintf(
				/* translators: 1: prefix, 2: file count */
				__( 'Delete %2$d files for backup "%1$s"?', 'blaminhor-essentials' ),
				$prefix,
				count( $found )
			),
			$assoc_args
		);

		$deleted = 0;
		foreach ( $found as $path ) {
			if ( @unlink( $path ) ) {
				$deleted++;
			}
		}

		blaminhor_essentials_log( sprintf( 'Backup "%s" deleted via WP-CLI (%d files).', $prefix, $deleted ), 'info', 'cli', array( 'event' => 'backup_delete' ) );
		WP_CLI::success( sprintf(
			/* translators: 1: file count, 2: prefix */
			__( '%1$d files removed for backup "%2$s".', 'blaminhor-essentials' ),
			$deleted,
			$prefix
		) );
	}
}
