<?php
/**
 * `wp powersuite settings` commands.
 *
 * @package WP_PowerSuite\CLI
 * @since 1.0.7
 */

namespace WP_PowerSuite\CLI;

use WP_PowerSuite\Core\ConfigService;

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

/**
 * Export and import WP PowerSuite configuration.
 *
 * @since 1.0.7
 */
final class SettingsCommand {

	/**
	 * Export WP PowerSuite configuration as JSON.
	 *
	 * Secrets (license keys, API keys, passwords, tokens) are redacted from the
	 * export and are never written to disk or STDOUT.
	 *
	 * ## OPTIONS
	 *
	 * [--file=<file>]
	 * : Write the export to this path instead of STDOUT.
	 *
	 * ## EXAMPLES
	 *
	 *     # Print to STDOUT
	 *     $ wp powersuite settings export
	 *
	 *     # Write to a file
	 *     $ wp powersuite settings export --file=powersuite-config.json
	 *
	 * @when after_wp_load
	 *
	 * @param array<int, string>   $args       Positional arguments (unused).
	 * @param array<string, mixed> $assoc_args Associative arguments.
	 * @return void
	 */
	public function export( array $args, array $assoc_args ): void {
		$service = new ConfigService();
		$export  = $service->exportConfig();

		$json = wp_json_encode( $export, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
		if ( ! is_string( $json ) ) {
			\WP_CLI::error( __( 'Failed to encode configuration to JSON.', 'wp-powersuite' ) );
		}

		$file = \WP_CLI\Utils\get_flag_value( $assoc_args, 'file', null );
		if ( $file === null ) {
			\WP_CLI::line( $json );
			return;
		}
		if ( ! is_string( $file ) || $file === '' ) {
			\WP_CLI::error( __( 'Provide a path with --file=<path>, or omit --file to print to STDOUT.', 'wp-powersuite' ) );
		}

		$dir = dirname( $file );
		if ( ! is_dir( $dir ) || ! wp_is_writable( $dir ) ) {
			\WP_CLI::error(
				sprintf(
					/* translators: %s: directory path */
					__( 'Directory is not writable: %s', 'wp-powersuite' ),
					$dir
				)
			);
		}

		// Create the file with owner-only perms before writing so a loose umask
		// never briefly exposes the export. Secrets are already redacted.
		$handle = @fopen( $file, 'cb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- intentional private create.
		if ( $handle === false ) {
			\WP_CLI::error(
				sprintf(
					/* translators: %s: file path */
					__( 'Failed to write export to: %s', 'wp-powersuite' ),
					$file
				)
			);
		}
		if ( function_exists( 'fchmod' ) ) {
			@fchmod( $handle, 0600 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- best-effort; not all filesystems support fchmod.
		} elseif ( function_exists( 'chmod' ) ) {
			@chmod( $file, 0600 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- best-effort hardening.
		}
		if ( ! flock( $handle, LOCK_EX ) || ftruncate( $handle, 0 ) === false || fwrite( $handle, $json ) === false ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- CLI export to a user-specified path.
			flock( $handle, LOCK_UN );
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- pairs with fopen() above.
			\WP_CLI::error(
				sprintf(
					/* translators: %s: file path */
					__( 'Failed to write export to: %s', 'wp-powersuite' ),
					$file
				)
			);
		}
		fflush( $handle );
		flock( $handle, LOCK_UN );
		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- pairs with fopen() above.

		\WP_CLI::success(
			sprintf(
				/* translators: %s: file path */
				__( 'Configuration exported to %s (secrets redacted).', 'wp-powersuite' ),
				$file
			)
		);
	}

	/**
	 * Import WP PowerSuite configuration from a JSON file.
	 *
	 * The file is validated (JSON, schema, slugs) before anything is written.
	 * Values are sanitized on import; sensitive options are never imported.
	 * Use --dry-run to preview the effect without touching the database.
	 *
	 * ## OPTIONS
	 *
	 * <file>
	 * : Path to a JSON file produced by `wp powersuite settings export`.
	 *
	 * [--dry-run]
	 * : Validate and report what would change, without modifying the database.
	 *
	 * [--replace]
	 * : Replace existing configuration instead of merging into it.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp powersuite settings import powersuite-config.json --dry-run
	 *     $ wp powersuite settings import powersuite-config.json
	 *     $ wp powersuite settings import powersuite-config.json --replace
	 *
	 * @when after_wp_load
	 *
	 * @param array<int, string>   $args       Positional arguments.
	 * @param array<string, mixed> $assoc_args Associative arguments.
	 * @return void
	 */
	public function import( array $args, array $assoc_args ): void {
		$file = isset( $args[0] ) ? (string) $args[0] : '';
		if ( $file === '' ) {
			\WP_CLI::error( __( 'Provide the path to a configuration JSON file.', 'wp-powersuite' ) );
		}
		if ( ! is_file( $file ) || ! is_readable( $file ) ) {
			\WP_CLI::error(
				sprintf(
					/* translators: %s: file path */
					__( 'File not found or not readable: %s', 'wp-powersuite' ),
					$file
				)
			);
		}

		$size = filesize( $file );
		if ( $size === false || $size > 25 * MB_IN_BYTES ) {
			\WP_CLI::error(
				$size === false
					? __( 'Unable to determine configuration file size. Import aborted.', 'wp-powersuite' )
					: __( 'Configuration file is too large (limit is 25 MB). This does not look like a PowerSuite export.', 'wp-powersuite' )
			);
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents
		$raw = file_get_contents( $file );
		if ( $raw === false ) {
			\WP_CLI::error( __( 'Unable to read the configuration file.', 'wp-powersuite' ) );
		}

		$data = json_decode( $raw, true );
		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $data ) ) {
			\WP_CLI::error(
				sprintf(
					/* translators: %s: JSON error message */
					__( 'Invalid JSON: %s', 'wp-powersuite' ),
					json_last_error_msg()
				)
			);
		}

		if ( ! isset( $data['config'] ) || ! is_array( $data['config'] ) || $data['config'] === [] ) {
			\WP_CLI::error( __( 'Invalid configuration file: missing "config" section.', 'wp-powersuite' ) );
		}

		$merge   = ! (bool) \WP_CLI\Utils\get_flag_value( $assoc_args, 'replace', false );
		$dry_run = (bool) \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run', false );

		if ( $dry_run ) {
			$this->reportDryRun( $data['config'], $merge );
			return;
		}

		$service = new ConfigService();
		$result  = $service->importConfig( $data, $merge );

		if ( empty( $result['success'] ) ) {
			\WP_CLI::error( (string) ( $result['message'] ?? __( 'Import failed.', 'wp-powersuite' ) ) );
		}

		if ( ! empty( $result['errors'] ) && is_array( $result['errors'] ) ) {
			foreach ( $result['errors'] as $error ) {
				\WP_CLI::warning( (string) $error );
			}
		}

		if ( ! empty( $result['warnings']['stripped_pro_modules'] ) && is_array( $result['warnings']['stripped_pro_modules'] ) ) {
			\WP_CLI::warning(
				sprintf(
					/* translators: %s: comma-separated module IDs */
					__( 'Premium modules skipped (no valid license): %s', 'wp-powersuite' ),
					implode( ', ', $result['warnings']['stripped_pro_modules'] )
				)
			);
		}

		\WP_CLI::success( (string) ( $result['message'] ?? __( 'Configuration imported.', 'wp-powersuite' ) ) );
	}

	/**
	 * Print a non-destructive summary of what an import would change.
	 *
	 * @param array<string, mixed> $config Config section from the import file.
	 * @param bool                 $merge  Whether the import would merge or replace.
	 * @return void
	 */
	private function reportDryRun( array $config, bool $merge ): void {
		$rows = [];

		if ( isset( $config['wp_powersuite_settings'] ) && is_array( $config['wp_powersuite_settings'] ) ) {
			$enabled = $config['wp_powersuite_settings']['enabled_modules'] ?? [];
			$rows[]  = [
				'item'   => 'wp_powersuite_settings',
				'action' => $merge ? 'merge' : 'replace',
				'detail' => is_array( $enabled ) ? sprintf( '%d enabled module(s)', count( $enabled ) ) : '—',
			];
		}

		if ( isset( $config['_wpps_redirects_table'] ) && is_array( $config['_wpps_redirects_table'] ) ) {
			$rows[] = [
				'item'   => 'redirects table',
				'action' => $merge ? 'upsert' : 'truncate + import',
				'detail' => sprintf( '%d row(s)', count( $config['_wpps_redirects_table'] ) ),
			];
		}

		if ( isset( $config['_wpps_short_links_table'] ) && is_array( $config['_wpps_short_links_table'] ) ) {
			$rows[] = [
				'item'   => 'short links table',
				'action' => $merge ? 'insert new' : 'truncate + import',
				'detail' => sprintf( '%d row(s)', count( $config['_wpps_short_links_table'] ) ),
			];
		}

		$skipped = [];
		foreach ( $config as $key => $value ) {
			if ( ! is_string( $key ) || strpos( $key, 'wp_powersuite_' ) !== 0 ) {
				continue;
			}
			if ( in_array( $key, [ 'wp_powersuite_settings', '_wpps_redirects_table', '_wpps_short_links_table' ], true ) ) {
				continue;
			}
			if ( \WP_PowerSuite\Core\SensitiveDataRedactor::isSensitiveKey( $key ) ) {
				$skipped[] = $key;
				continue;
			}
			$rows[] = [
				'item'   => $key,
				'action' => $merge ? 'merge' : 'overwrite',
				'detail' => is_array( $value ) ? sprintf( '%d key(s)', count( $value ) ) : gettype( $value ),
			];
		}

		if ( $rows === [] ) {
			\WP_CLI::warning( __( 'Dry run: nothing importable found in this file.', 'wp-powersuite' ) );
			return;
		}

		\WP_CLI\Utils\format_items( 'table', $rows, [ 'item', 'action', 'detail' ] );

		if ( $skipped !== [] ) {
			\WP_CLI::log(
				sprintf(
					/* translators: %s: comma-separated option names */
					__( 'Skipped (sensitive, never imported): %s', 'wp-powersuite' ),
					implode( ', ', $skipped )
				)
			);
		}

		\WP_CLI::success( __( 'Dry run complete. No changes were written.', 'wp-powersuite' ) );
	}
}
