<?php

defined( 'ABSPATH' ) || exit;

/**
 * Import / Export module.
 *
 * Backup and restore the entire BW Dev configuration (`bw_dev_settings`) as a
 * JSON file. Designed for moving config between a fleet of client sites:
 *  - Export downloads a JSON file with site metadata + the full settings dump.
 *  - Import accepts a previously exported file, validates structure, and runs
 *    every section through its module's `sanitize()` before writing — so an
 *    altered or malicious file can't sneak unfiltered values into the option.
 *
 * Actions are handled inline on `admin_init` (rather than via admin-post.php)
 * because the settings tab is already an admin context with full auth + caps.
 * This avoids the `wp_validate_auth_cookie()` quirks that bit the 1.8.x
 * purge-button experiments.
 *
 * No persisted settings of its own. Module exists purely as an action-only tab.
 *
 * @package BW_Dev
 */

class BW_Dev_Module_Import_Export implements BW_Dev_Module_Interface {

	const QUERY_PARAM     = 'bw_dev_io_action';
	const EXPORT_NONCE    = 'bw_dev_io_export';
	const IMPORT_NONCE    = 'bw_dev_io_import';
	const FORMAT_NAME     = 'bw-dev-settings';
	const FORMAT_VERSION  = 1;
	const MAX_UPLOAD_SIZE = 2097152; // 2 MB — generous for a JSON config.

	public function slug(): string {
		return 'import_export';
	}

	public function label(): string {
		return __( 'Import / Export', 'bw-dev' );
	}

	public function group(): string {
		return 'core';
	}

	public function default_settings(): array {
		return array();
	}

	public function sanitize( array $data ): array {
		return array();
	}

	public function register(): void {
		add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
	}

	/* ---------------------------------------------------------------------
	 * Action dispatch
	 * ------------------------------------------------------------------- */

	public function maybe_handle_action(): void {
		if ( ! is_admin() ) {
			return;
		}
		$query = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page  = isset( $query['page'] ) ? sanitize_key( (string) $query['page'] ) : '';
		if ( BW_Dev_Admin_Page::MENU_SLUG !== $page ) {
			return;
		}
		$action = isset( $query[ self::QUERY_PARAM ] ) ? sanitize_key( (string) $query[ self::QUERY_PARAM ] ) : '';
		if ( '' === $action ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to do this.', 'bw-dev' ), '', array( 'response' => 403 ) );
		}
		if ( 'export' === $action ) {
			check_admin_referer( self::EXPORT_NONCE );
			$this->handle_export();
			return;
		}
		if ( 'import' === $action ) {
			check_admin_referer( self::IMPORT_NONCE );
			$this->handle_import();
			return;
		}
	}

	/* ---------------------------------------------------------------------
	 * Export
	 * ------------------------------------------------------------------- */

	private function handle_export(): void {
		$settings = (array) get_option( BW_Dev_Settings::OPTION, array() );

		$payload = array(
			'_meta'    => array(
				'format'         => self::FORMAT_NAME,
				'format_version' => self::FORMAT_VERSION,
				'plugin_version' => defined( 'BW_DEV_VERSION' ) ? BW_DEV_VERSION : '',
				'exported_at'    => gmdate( 'c' ),
				'source_site'    => home_url( '/' ),
			),
			'settings' => $settings,
		);

		$filename = 'bw-dev-settings-' . gmdate( 'Y-m-d-His' ) . '.json';
		nocache_headers();
		header( 'Content-Type: application/json; charset=' . get_bloginfo( 'charset' ) );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		echo wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		exit;
	}

	/* ---------------------------------------------------------------------
	 * Import
	 * ------------------------------------------------------------------- */

	private function handle_import(): void {
		$redirect_base = add_query_arg(
			array(
				'page' => BW_Dev_Admin_Page::MENU_SLUG,
				'tab'  => $this->slug(),
			),
			admin_url( 'options-general.php' )
		);

		$files = $_FILES; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce already checked above.
		if ( empty( $files['bw_dev_io_file']['tmp_name'] ) || ! is_uploaded_file( $files['bw_dev_io_file']['tmp_name'] ) ) {
			$this->redirect_with_flash( $redirect_base, 'error', __( 'No file uploaded.', 'bw-dev' ) );
		}
		$size = isset( $files['bw_dev_io_file']['size'] ) ? (int) $files['bw_dev_io_file']['size'] : 0;
		if ( $size <= 0 || $size > self::MAX_UPLOAD_SIZE ) {
			/* translators: %s: human-readable max size */
			$this->redirect_with_flash( $redirect_base, 'error', sprintf( __( 'Upload rejected — file is empty or larger than %s.', 'bw-dev' ), size_format( self::MAX_UPLOAD_SIZE ) ) );
		}

		$contents = file_get_contents( $files['bw_dev_io_file']['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false === $contents || '' === $contents ) {
			$this->redirect_with_flash( $redirect_base, 'error', __( 'Upload could not be read.', 'bw-dev' ) );
		}

		$decoded = json_decode( (string) $contents, true );
		if ( ! is_array( $decoded ) ) {
			$this->redirect_with_flash( $redirect_base, 'error', __( 'File is not valid JSON.', 'bw-dev' ) );
		}

		// Structure check: must have a recognised _meta.format and a settings array.
		$format = isset( $decoded['_meta']['format'] ) ? (string) $decoded['_meta']['format'] : '';
		if ( self::FORMAT_NAME !== $format ) {
			$this->redirect_with_flash( $redirect_base, 'error', __( 'File is not a BW Dev settings export.', 'bw-dev' ) );
		}
		$incoming = isset( $decoded['settings'] ) && is_array( $decoded['settings'] ) ? $decoded['settings'] : null;
		if ( null === $incoming ) {
			$this->redirect_with_flash( $redirect_base, 'error', __( 'File is missing a "settings" payload.', 'bw-dev' ) );
		}

		$merged = $this->apply_imported_settings( $incoming );
		update_option( BW_Dev_Settings::OPTION, $merged );

		$this->redirect_with_flash( $redirect_base, 'imported', __( 'Settings imported.', 'bw-dev' ) );
	}

	private function redirect_with_flash( string $base, string $status, string $message ): void {
		$url = add_query_arg(
			array(
				'bw_dev_io_status'  => $status,
				'bw_dev_io_message' => rawurlencode( $message ),
			),
			$base
		);
		wp_safe_redirect( $url );
		exit;
	}

	/**
	 * Run every recognised section of the imported payload through the
	 * matching module's `sanitize()` before merging into the option. Modules
	 * that aren't in the imported payload keep their current settings.
	 */
	private function apply_imported_settings( array $imported ): array {
		$current = (array) get_option( BW_Dev_Settings::OPTION, array() );
		$modules = bw_dev()->modules();

		// Brand block — sanitised the same way the settings dispatch sanitize() does.
		if ( isset( $imported['brand'] ) && is_array( $imported['brand'] ) ) {
			$brand            = $imported['brand'];
			$current['brand'] = array(
				'plugin_display_name'  => isset( $brand['plugin_display_name'] ) ? sanitize_text_field( (string) $brand['plugin_display_name'] ) : '',
				'block_category_label' => isset( $brand['block_category_label'] ) ? sanitize_text_field( (string) $brand['block_category_label'] ) : '',
				'block_title_prefix'   => isset( $brand['block_title_prefix'] ) ? sanitize_text_field( (string) $brand['block_title_prefix'] ) : '',
			);
		}

		// Module-enable map.
		if ( isset( $imported['modules'] ) && is_array( $imported['modules'] ) ) {
			$known      = array();
			$incoming_m = $imported['modules'];
			foreach ( $modules as $m ) {
				$slug = $m->slug();
				if ( array_key_exists( $slug, $incoming_m ) ) {
					$known[ $slug ] = (bool) $incoming_m[ $slug ];
				}
			}
			if ( ! empty( $known ) ) {
				$current['modules'] = isset( $current['modules'] ) && is_array( $current['modules'] )
					? array_merge( $current['modules'], $known )
					: $known;
			}
		}

		// Per-module sections — dispatch through each module's own sanitize().
		foreach ( $modules as $module ) {
			$slug = $module->slug();
			if ( ! isset( $imported[ $slug ] ) || ! is_array( $imported[ $slug ] ) ) {
				continue;
			}
			$current[ $slug ] = $module->sanitize( $imported[ $slug ] );
		}

		return $current;
	}

	/* ---------------------------------------------------------------------
	 * Settings tab
	 * ------------------------------------------------------------------- */

	public function render_tab(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$query     = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$status    = isset( $query['bw_dev_io_status'] ) ? sanitize_key( (string) $query['bw_dev_io_status'] ) : '';
		$flash_msg = isset( $query['bw_dev_io_message'] ) ? (string) $query['bw_dev_io_message'] : '';

		$export_url = wp_nonce_url(
			add_query_arg(
				array(
					'page'             => BW_Dev_Admin_Page::MENU_SLUG,
					'tab'              => $this->slug(),
					self::QUERY_PARAM  => 'export',
				),
				admin_url( 'options-general.php' )
			),
			self::EXPORT_NONCE
		);
		$import_url = add_query_arg(
			array(
				'page'             => BW_Dev_Admin_Page::MENU_SLUG,
				'tab'              => $this->slug(),
				self::QUERY_PARAM  => 'import',
			),
			admin_url( 'options-general.php' )
		);
		?>
		<p class="description">
			<?php esc_html_e( 'Back up or move BW Dev configuration between client sites. Export downloads a JSON file with the full settings dump; import accepts a previously exported file.', 'bw-dev' ); ?>
		</p>

		<?php if ( 'imported' === $status ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php echo esc_html( $flash_msg ?: __( 'Settings imported.', 'bw-dev' ) ); ?></p></div>
		<?php elseif ( 'error' === $status ) : ?>
			<div class="notice notice-error is-dismissible"><p><strong><?php esc_html_e( 'Import failed.', 'bw-dev' ); ?></strong> <?php echo esc_html( $flash_msg ); ?></p></div>
		<?php endif; ?>

		<h3><?php esc_html_e( 'Export', 'bw-dev' ); ?></h3>
		<p>
			<?php esc_html_e( 'Downloads the entire BW Dev configuration as a JSON file. Use this to back up before changes, or to copy settings to another site.', 'bw-dev' ); ?>
		</p>
		<p>
			<a class="button button-primary" href="<?php echo esc_url( $export_url ); ?>">
				<span class="dashicons dashicons-download" style="font-size:16px;width:16px;height:16px;vertical-align:-3px;margin-right:4px;"></span>
				<?php esc_html_e( 'Download settings as JSON', 'bw-dev' ); ?>
			</a>
		</p>
		<p class="description">
			<?php esc_html_e( 'The exported file contains every module\'s settings plus the white-label branding config. It does NOT contain the login activity log, scheduled-post-action post meta, or widgets attached to sidebars (those are content, not config).', 'bw-dev' ); ?>
		</p>

		<h3 style="margin-top:30px;"><?php esc_html_e( 'Import', 'bw-dev' ); ?></h3>
		<div style="background:#fff;border-left:4px solid #dba617;padding:12px 16px;margin:14px 0;max-width:720px;">
			<p style="margin:0 0 6px;"><strong><?php esc_html_e( 'Importing overwrites your current settings.', 'bw-dev' ); ?></strong></p>
			<p style="margin:0;color:#1d2327;">
				<?php esc_html_e( 'Click "Download settings as JSON" above first if there\'s any chance you might want to roll back.', 'bw-dev' ); ?>
			</p>
		</div>
		<form action="<?php echo esc_url( $import_url ); ?>" method="post" enctype="multipart/form-data" style="max-width:720px;">
			<?php wp_nonce_field( self::IMPORT_NONCE ); ?>
			<p>
				<label for="bw-dev-io-file"><strong><?php esc_html_e( 'BW Dev settings JSON file', 'bw-dev' ); ?></strong></label><br />
				<input type="file" id="bw-dev-io-file" name="bw_dev_io_file" accept=".json,application/json" required />
			</p>
			<p>
				<button type="submit" class="button button-secondary" onclick="<?php echo esc_attr( 'return confirm(' . wp_json_encode( __( 'Overwrite current BW Dev settings with the contents of this file?', 'bw-dev' ) ) . ');' ); ?>">
					<span class="dashicons dashicons-upload" style="font-size:16px;width:16px;height:16px;vertical-align:-3px;margin-right:4px;"></span>
					<?php esc_html_e( 'Upload and import', 'bw-dev' ); ?>
				</button>
			</p>
		</form>

		<h3 style="margin-top:30px;"><?php esc_html_e( 'How it works', 'bw-dev' ); ?></h3>
		<ul style="list-style:disc;margin-left:20px;">
			<li><?php esc_html_e( 'The export is a JSON file: a top-level "_meta" block (plugin version + exported_at timestamp + source site URL) and a "settings" block with the full bw_dev_settings option dump.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'On import, every section is run through its module\'s own sanitize() before being saved — so a hand-edited or malicious file can\'t sneak unfiltered values into the option.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Modules NOT present in the imported file keep their current settings — partial imports are supported (you can export from one site and merge selectively by editing the JSON before re-importing).', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Format version is "1" — future format changes (if any) will increment this and remain backwards-compatible where possible.', 'bw-dev' ); ?></li>
		</ul>
		<?php
	}

	public function uninstall(): void {
		// No persisted state.
	}
}
