<?php
/**
 * Import Form Handler Class.
 *
 * @package EaLab\Wp_Deployer
 */

namespace EaLab\WP_Deployer\includes\controller;

use ActionScheduler_Store;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\Exception\UnmetDependencyException;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\RootDependencyDefinition;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Batch\BatchScheduler;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\WpDeployException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Import\Importer;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\CommonDataStorage;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Date;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\FileManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\PostFormDataAsJsonParser;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\Uuid;
use Throwable;

/**
 * Import Form Handler Class.
 */
class Import_Form_Handler extends ComponentBasedObject {
	/**
	 * The Importer for Deploy & Sync Content.
	 */
	private ?Importer $importer = null;

	/**
	 * The file manager for Deploy & Sync Content.
	 */
	private ?FileManager $file_manager = null;

	/**
	 * Available subdirectories.
	 *
	 * @var array<string,array<string,mixed>>
	 *
	 * @deprecated Useless property.
	 */
	private array $subdirectories;

    public function getDependencyDefinitions(): array {
	    $definitions = parent::getDependencyDefinitions();

		$definitions[] = new RootDependencyDefinition(Importer::class, $this->importer);
		$definitions[] = new RootDependencyDefinition(FileManager::class, $this->file_manager);

		return $definitions;
    }

	protected function onReady(): void {
		parent::onReady();
		$this->subdirectories = $this->file_manager->getExistingExportedDataSubdirectories( 'import' );
	}

	/**
	 * Callback which finalizes the import batch for UI import.
	 *
	 * @param string $batch_id Batch ID.
	 * @param string $bundle_id Bundle ID.
	 *
	 * @throws Throwable Unmanaged errors.
	 * @throws WpDeployException Deployer errors.
	 */
	public static function finalizeImportBatch( string $batch_id, string $bundle_id ): void {
		// Messages with time that will be shown to the user.
		$step_messages = array();

		$progress_callback = static function ( array $progress ) use ( $batch_id, &$step_messages ) {
			$message      = $progress['message'] ?? null;
			$message_line = $progress['message_line'] ?? null;

			$log_messages = static function ( $message ) use ( $batch_id, &$step_messages, &$common_data_storage ) {
				$current_date = Date::getCurrentDateTime()->format( Date::DB_DATETIME_FORMAT_MILLISECONDS );

				$message = is_string( $message ) ? $message : gettype( $message );

				// Add the line to the messages of this step.
				$step_messages[] = $current_date . ': ' . $message;
			};

			! empty( $message ) && $log_messages( $message );
			! empty( $message_line ) && $log_messages( $message_line );
		};

		try {
			ComponentBasedObject::getRootComponentByClassName( Importer::class, true )->finalizeBundleImport( $bundle_id, $progress_callback );
		} finally {
			$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

			// Build the step results.
			$batch_step_result = array(
				'step_messages' => $step_messages,
			);

			$common_data_storage->store( $batch_id, json_encode( $batch_step_result ), 'deploy_sync_content/batch_step_result' );
		}
	}

	/**
	 * Callback which prepares the import batch for UI import.
	 *
	 * @param string        $batch_id Batch ID.
	 * @param string        $subdirectory Bundle ID (subdirectory).
	 * @param array<string> $complete_list The complete item full UUIDs list.
	 * @param bool          $force_import Whether the import must be forced or not.
	 *
	 * @throws Throwable Unmanaged errors.
	 * @throws WpDeployException Deployer errors.
	 */
	public static function prepareImportBatch( string $batch_id, string $subdirectory, array $complete_list, bool $force_import ): void {
		// Messages with time that will be shown to the user.
		$step_messages = array();

		$progress_callback = static function ( array $progress ) use ( $batch_id, &$step_messages ) {
			$message      = $progress['message'] ?? null;
			$message_line = $progress['message_line'] ?? null;

			$log_messages = static function ( $message ) use ( $batch_id, &$step_messages, &$common_data_storage ) {
				$current_date = Date::getCurrentDateTime()->format( Date::DB_DATETIME_FORMAT_MILLISECONDS );

				$message = is_string( $message ) ? $message : gettype( $message );

				// Add the line to the messages of this step.
				$step_messages[] = $current_date . ': ' . $message;
			};

			! empty( $message ) && $log_messages( $message );
			! empty( $message_line ) && $log_messages( $message_line );
		};

		try {
			ComponentBasedObject::getRootComponentByClassName( Importer::class, true )->prepareImport( $subdirectory, $complete_list, $force_import, $progress_callback );
		} finally {
			$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

			// Build the step results.
			$batch_step_result = array(
				'step_messages' => $step_messages,
			);

			$common_data_storage->store( $batch_id, json_encode( $batch_step_result ), 'deploy_sync_content/batch_step_result' );
		}
	}

	/**
	 * Callback which processes a step of the import batch for UI import.
	 *
	 * @param string $batch_id Batch ID.
	 * @param string $bundle_id Bundle ID.
	 * @param array  $item_full_uuids The selected item full UUIDs list. Will be expanded with dependencies.
	 * @param bool   $force_import Whether the import must be forced or not.
	 * @param array  $finalize_step_args Arguments of the import finalization step,
	 *     to use when all import steps of this batch_id have finished execution (successful or not).
	 *
	 * @throws Throwable Unmanaged errors.
	 * @throws WpDeployException Deployer errors.
	 */
	public static function processImportBatchStep( string $batch_id, string $bundle_id, array $item_full_uuids, bool $force_import, array $finalize_step_args ): void {
		$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

		// Messages with time that will be shown to the user.
		$step_messages = array();

		// The list of items that have been processed in this step.
		$processed_item_uuids = array();

		$progress_callback = static function ( array $progress = array() ) use ( $batch_id, &$step_messages, &$common_data_storage, &$processed_item_uuids ) {
			$total_count    = $progress['total_count'] ?? null;
			$tick           = $progress['tick'] ?? null;
			$processed_item = $progress['processed_item'] ?? null;
			$message        = $progress['message'] ?? null;
			$message_line   = $progress['message_line'] ?? null;

			$log_messages = static function ( $message ) use ( $batch_id, &$step_messages, &$common_data_storage ) {
				$current_date = Date::getCurrentDateTime()->format( Date::DB_DATETIME_FORMAT_MILLISECONDS );

				$message = is_string( $message ) ? $message : gettype( $message );

				// Add the line to the messages of this step.
				$step_messages[] = $current_date . ': ' . $message;
			};

			! empty( $message ) && $log_messages( $message );
			! empty( $message_line ) && $log_messages( $message_line );

			// Metadata specific to the import process.
			if ( ! empty( $processed_item ) ) {
				$processed_item_uuids[ $processed_item ] = $processed_item;
			}
		};

		$importer = ComponentBasedObject::getRootComponentByClassName( Importer::class, true );

		// Expand to the dependencies.
		$expanded = $importer->expandImportListToDependencies( $bundle_id, $item_full_uuids );

		// Get the metadata of the entire batch process.
		$stored_info = $common_data_storage->loadSingleValue( $batch_id, 'deploy_sync_content/import_meta' ) ?? '[]';
		$decoded     = json_decode( $stored_info, true ) ?? array();

		// This is a list to prevent items from being imported multiple times across the steps.
		$already_processed_list = $decoded['processed_item_uuid_list'] ?? array();

		// Load the items data from the bundle files.
		$file_manager = ComponentBasedObject::getRootComponentByClassName( FileManager::class, true );

		$items_to_import = $file_manager->loadDeploymentItemsData( $bundle_id, $expanded );

		try {
			// Finally import the items.
			$importer->importItems( $items_to_import, $force_import, $progress_callback, $already_processed_list );
		} finally {
			// Build the step results.
			$batch_step_result = array(
				'step_messages' => $step_messages,
			);

			$common_data_storage->store( $batch_id, json_encode( $batch_step_result ), 'deploy_sync_content/batch_step_result' );
		}

		// Update the import batch metadata.
		// The already_processed_list variable is not automatically updated because importItems sends it
		// in the options array, which does not edit the value by reference.
		// The only way to be informed about the processed list in the current step is by
		// relying on the 'processed_item' value set by the progress_callback.
		$decoded['processed_item_uuid_list'] = array_merge( $already_processed_list, $processed_item_uuids );

		// remaining_import_steps_count was decreased in the action_scheduler_begin_execute hook.
		if ( empty( $decoded['finalized'] ) && 0 >= $decoded['remaining_import_steps_count'] ) {
			// This finalized value is needed to limit a concurrency issue
			// that may finalize multiple times the bundle import.
			$decoded['finalized'] = true;

			// All import steps have been executed.
			// Enqueue the finalize step.
			$batch_scheduler = ComponentBasedObject::getRootComponentByClassName( BatchScheduler::class, true );

			$batch_scheduler->enqueueStepToBatch( ...$finalize_step_args );
		}

		$common_data_storage->store( $batch_id, json_encode( $decoded ), 'deploy_sync_content/import_meta', true );
	}

	/**
	 * Registers callbacks for action scheduler.
	 *
	 * @throws Throwable Unmanaged errors.
	 */
	public static function registerActionSchedulerCallbacks(): void {
		add_action(
			'action_scheduler_begin_execute',
			static function ( $action_id, $context ) {
				$store  = ActionScheduler_Store::instance();
				$action = $store->fetch_action( $action_id );

				if ( 'deploy_sync_content/process_batch_step' !== $action->get_hook() ) {
					// Not an action we want to process.
					return;
				}

				$args = $action->get_args();

				if ( array( static::class, 'processImportBatchStep' ) !== $args[0] ) {
					// Not a step we want to process.
					return;
				}

				// An import step has started. We need to decrease the value of the remaining steps.
				// Doing it here will make sure that the value decreases even in case of failures.
				$batch_id = $action->get_group();

				// First, get the metadata for the batch.
				$common_data_storage = ComponentBasedObject::getRootComponentByClassName( CommonDataStorage::class, true );

				$stored_info = $common_data_storage->loadSingleValue( $batch_id, 'deploy_sync_content/import_meta' ) ?? '[]';
				$decoded     = json_decode( $stored_info, true ) ?? array();

				// Decrease the import steps count.
				--$decoded['remaining_import_steps_count'];

				// Write back the value.
				$common_data_storage->store( $batch_id, json_encode( $decoded ), 'deploy_sync_content/import_meta', true );
			},
			10,
			2
		);
	}
}
