/**
 * ASE Migration: worker keepalive mu-plugin (auto-generated, self-deleting).
 *
 * Ensures the migration worker can be re-kicked even when other plugins
 * cause fatal errors during WordPress bootstrap (which prevents ASE's
 * regular AJAX hooks from being registered).
 *
 * @since 8.7.0
 */

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

/**
 * Handle the migration worker kick request.
 *
 * Reads state from the file system and directly invokes process_migration().
 */
function asenha_mu_kick_migration_worker() {
	$migration_id = isset( $_POST['migration_id'] ) ? sanitize_text_field( wp_unslash( $_POST['migration_id'] ) ) : '';

	// Validate UUID format.
	if ( empty( $migration_id ) || ! wp_is_uuid( $migration_id ) ) {
		wp_send_json_error( array( 'message' => 'Invalid migration ID.' ) );
	}

	// Locate the state file via the restore-dirs mapping (lives in wp-content/).
	$mapping_file = WP_CONTENT_DIR . '/asenha-restore-dirs.json';
	$backup_dir   = '';
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists
	if ( file_exists( $mapping_file ) ) {
		$raw = file_get_contents( $mapping_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false !== $raw ) {
			$mappings = json_decode( $raw, true );
			if ( is_array( $mappings ) && isset( $mappings[ $migration_id ] ) ) {
				$entry = $mappings[ $migration_id ];
				if ( is_array( $entry ) && ! empty( $entry['backup_dir'] ) && is_string( $entry['backup_dir'] ) ) {
					$backup_dir = $entry['backup_dir'];
				} elseif ( is_string( $entry ) ) {
					// Legacy fallback: plain string mapping.
					$backup_dir = $entry;
				}
			}
		}
	}

	if ( '' === $backup_dir || ! is_dir( $backup_dir ) ) {
		wp_send_json_error( array( 'message' => 'Migration backup directory not found.' ) );
	}

	$state_file = $backup_dir . '/restore_state_' . sanitize_file_name( $migration_id ) . '.json';
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists
	if ( ! file_exists( $state_file ) ) {
		wp_send_json_error( array( 'message' => 'Migration state file not found.' ) );
	}

	$raw_state = file_get_contents( $state_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
	if ( false === $raw_state ) {
		wp_send_json_error( array( 'message' => 'Failed to read migration state file.' ) );
	}

	$state = json_decode( $raw_state, true );
	if ( ! is_array( $state ) ) {
		wp_send_json_error( array( 'message' => 'Invalid migration state.' ) );
	}

	$current_step = isset( $state['current_step'] ) ? (string) $state['current_step'] : '';
	$status       = isset( $state['status'] ) ? (string) $state['status'] : '';

	// Self-delete when migration is in a final state.
	$final_states = array( 'completed', 'failed', 'cancelled' );
	if ( in_array( $status, $final_states, true ) ) {
		@unlink( __FILE__ ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		wp_send_json_success( array( 'kicked' => false, 'message' => 'Migration is in a final state; mu-plugin removed.' ) );
	}

	// Only kick non-database, non-final steps. Database step has its own runner.
	$skip_steps = array( 'database', 'completed', 'failed', 'cancelled', 'redirect', '' );
	if ( in_array( $current_step, $skip_steps, true ) ) {
		wp_send_json_success( array( 'kicked' => false, 'message' => 'Step "' . $current_step . '" not applicable for worker kick.' ) );
	}

	// Stale guard: only kick when last_update is at least 30 seconds old.
	$last_update = isset( $state['last_update'] ) ? (int) $state['last_update'] : 0;
	$now         = time();
	if ( $last_update > 0 && ( $now - $last_update ) < 30 ) {
		wp_send_json_success( array( 'kicked' => false, 'message' => 'Worker is still active.' ) );
	}

	// Rate limit: 10 second throttle via state field.
	$last_kick_at = isset( $state['mu_worker_kick_last_at'] ) ? (int) $state['mu_worker_kick_last_at'] : 0;
	if ( $last_kick_at > 0 && ( $now - $last_kick_at ) < 10 ) {
		wp_send_json_success( array( 'kicked' => false, 'throttled' => true, 'message' => 'Throttled.' ) );
	}

	// Update only kick tracking fields; avoid stomping other state keys
	// (especially files_extract_done) that a concurrent worker may have set.
	$state['mu_worker_kick_last_at'] = $now;
	$state['mu_worker_kick_count']   = isset( $state['mu_worker_kick_count'] ) ? ( (int) $state['mu_worker_kick_count'] + 1 ) : 1;

	// Atomic write via temp+rename to prevent corrupted JSON if the process
	// is killed mid-write (which would cause subsequent reads to return false
	// and lose all state, including files_extract_done).
	$mu_tmp_file = $state_file . '.tmp.' . getmypid();
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
	$mu_written = @file_put_contents( $mu_tmp_file, wp_json_encode( $state ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	if ( false !== $mu_written ) {
		// phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
		@rename( $mu_tmp_file, $state_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	} else {
		// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
		@unlink( $mu_tmp_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	}

	// Load ASE migration classes and directly invoke process_migration().
	$ase_path = '%%ASE_PLUGIN_PATH%%';
	$site_backup_file = $ase_path . '/includes/premium/site-backup/site-backup.php';

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists
	if ( ! file_exists( $site_backup_file ) ) {
		wp_send_json_error( array( 'message' => 'ASE site-backup module not found.' ) );
	}

	// Ensure the script continues even if user disconnects.
	ignore_user_abort( true );
	if ( function_exists( 'set_time_limit' ) ) {
		set_time_limit( 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	}
	if ( function_exists( 'wp_raise_memory_limit' ) ) {
		wp_raise_memory_limit( 'admin' );
	}

	// Load the migration classes.
	// Normally ASENHA_PATH and ASENHA_VERSION are defined by the main plugin file,
	// but guard against edge cases where the main plugin didn't fully load.
	if ( ! defined( 'ASENHA_PATH' ) ) {
		define( 'ASENHA_PATH', $ase_path . '/' );
	}
	if ( ! defined( 'ASENHA_VERSION' ) ) {
		define( 'ASENHA_VERSION', '0.0.0' );
	}
	require_once $site_backup_file;

	// Register redacted last_fatal_* recorder before extract/work can fatal.
	if ( class_exists( 'ASENHA_Site_Backup_Helper' ) ) {
		$mu_helper = new ASENHA_Site_Backup_Helper();
		$mu_helper->register_migration_fatal_recorder( $migration_id, $backup_dir );
	}

	// Read migration parameters from state.
	$filename = '';
	if ( isset( $state['filename'] ) ) {
		if ( class_exists( 'ASENHA_Site_Backup_Helper' ) ) {
			$filename = ( new ASENHA_Site_Backup_Helper() )->sanitize_backup_filename_basename( (string) $state['filename'] );
		} else {
			$filename = sanitize_file_name( (string) $state['filename'] );
		}
	}
	$components = ( isset( $state['components'] ) && is_array( $state['components'] ) )
		? array_map( 'sanitize_text_field', $state['components'] )
		: array( 'all' );
	$search_replace_config = ( isset( $state['search_replace_config'] ) && is_array( $state['search_replace_config'] ) )
		? $state['search_replace_config']
		: array();

	// Invoke one bounded migration chunk with allow_spawn=true so it can continue.
	$migrate = new ASENHA_Site_Backup_Migrate();
	$result  = $migrate->process_migration( $migration_id, $filename, $components, $search_replace_config, true );

	$kicked = ! is_wp_error( $result );

	wp_send_json_success( array(
		'kicked'  => $kicked,
		'message' => $kicked ? 'Migration worker chunk executed.' : 'Migration worker chunk failed: ' . $result->get_error_message(),
		'source'  => 'mu-plugin',
	) );
}

// Register the handler at priority 1 so it fires before any regular plugin hooks.
add_action( 'wp_ajax_nopriv_asenha_kick_migration_worker_noauth', 'asenha_mu_kick_migration_worker', 1 );
add_action( 'wp_ajax_asenha_kick_migration_worker_noauth', 'asenha_mu_kick_migration_worker', 1 );

// Self-cleanup: on every page load, check if any active migration exists.
// If not, remove this mu-plugin to keep the installation clean.
add_action( 'shutdown', function() {
	$mapping_file = WP_CONTENT_DIR . '/asenha-restore-dirs.json';
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists
	if ( ! file_exists( $mapping_file ) ) {
		@unlink( __FILE__ ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		return;
	}

	$raw = file_get_contents( $mapping_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
	if ( false === $raw ) {
		return;
	}

	$mappings = json_decode( $raw, true );
	if ( ! is_array( $mappings ) || empty( $mappings ) ) {
		@unlink( __FILE__ ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	}
}, 9999 );
