<?php

namespace SugarCalendarBookings\Migration;

use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;
use Exception;
use WP_Error;

/**
 * Setup Wizard Migration Handler.
 *
 * Handles migration orchestration for the Setup Wizard REST API context.
 * Encapsulates validation and response formatting,
 * delegating batch processing to MigrationService.
 *
 * @since 1.3.1
 */
class SetupWizardMigrationHandler {

	/**
	 * Migration service.
	 *
	 * @since 1.3.1
	 *
	 * @var MigrationService
	 */
	private MigrationService $service;

	/**
	 * Migration registry.
	 *
	 * @since 1.3.1
	 *
	 * @var MigrationRegistry
	 */
	private MigrationRegistry $registry;

	/**
	 * Constructor.
	 *
	 * @since 1.3.1
	 *
	 * @param MigrationService  $service  Migration service.
	 * @param MigrationRegistry $registry Migration registry.
	 */
	public function __construct( MigrationService $service, MigrationRegistry $registry ) {

		$this->service  = $service;
		$this->registry = $registry;
	}

	/**
	 * Start a migration.
	 *
	 * Validates the source, checks entity dependencies, initializes migration state,
	 * and returns entity counts for the selected entities.
	 *
	 * @since 1.3.1
	 *
	 * @param string        $source_id Source adapter ID.
	 * @param array<string> $entities  Entity types to migrate.
	 *
	 * @return array<string, mixed>|WP_Error Migration start data or error.
	 */
	public function start( string $source_id, array $entities ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$adapter = $this->registry->get_adapter( $source_id );

		if ( ! $adapter ) {
			return new WP_Error(
				'invalid_source',
				esc_html__( 'Invalid migration source.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		// Validate source has data.
		$validation = $this->service->validate_source( $adapter );

		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		// Validate requested entities are supported.
		$supported = $adapter->get_supported_entities();

		foreach ( $entities as $entity ) {
			if ( ! in_array( $entity, $supported, true ) ) {
				return new WP_Error(
					'unsupported_entity',
					sprintf(
						/* translators: %s - entity type name. */
						esc_html__( 'Unsupported entity type: %s', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
						$entity
					)
				);
			}
		}

		// Validate entity dependencies.
		$dependency_error = $this->validate_entity_dependencies( $entities, $source_id );

		if ( is_wp_error( $dependency_error ) ) {
			return $dependency_error;
		}

			// Calculate excluded entities (all supported minus selected).
		$exclude_entities = array_diff( $supported, $entities );

		// Get entity counts and initialize state.
		$entity_data = $this->service->get_entity_counts( $adapter, $exclude_entities );

		$this->service->initialize_state( $source_id, MigrationService::DEFAULT_BATCH_SIZE, $entity_data );

		// Build simple counts map for response.
		$counts = [];

		foreach ( $entity_data['entities'] as $type => $info ) {
			$counts[ $type ] = $info['total'];
		}

		return [
			'total_items' => $entity_data['total_records'],
			'entities'    => $counts,
		];
	}

	/**
	 * Process the next migration batch.
	 *
	 * Validates the request, delegates batch processing to MigrationService,
	 * and formats the progress response for the wizard.
	 *
	 * @since 1.3.1
	 *
	 * @param string $source_id Source adapter ID from request (used for validation).
	 *
	 * @return array<string, mixed>|WP_Error Batch progress data or error.
	 */
	public function process_batch( string $source_id ) {

		$state = $this->service->get_state();

		if ( ! $state || $state['status'] !== 'running' ) {
			return new WP_Error(
				'no_active_migration',
				esc_html__( 'No active migration found.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		// Validate request source matches state source.
		if ( $source_id !== $state['source_id'] ) {
			return new WP_Error(
				'source_mismatch',
				esc_html__( 'Migration source mismatch.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		$adapter = $this->registry->get_adapter( $state['source_id'] );

		if ( ! $adapter ) {
			$this->service->abort_migration( esc_html__( 'Invalid migration source.', 'sugar-calendar-bookings-scheduling-appointments-lite' ) );

			return $this->build_failure_response(
				esc_html__( 'Invalid migration source.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		try {
			$result = $this->service->step_batch( $state, $adapter );

			// step_batch() already calls abort_migration() on fetch errors.
			if ( is_wp_error( $result ) ) {
				return $this->build_failure_response( $result->get_error_message() );
			}

			return $this->build_progress_response( $result['state'], $result['is_complete'], $result['current_entity'] );
		} catch ( Exception $e ) {
			$this->service->abort_migration( $e->getMessage() );

			return $this->build_failure_response(
				esc_html__( 'An error occurred while processing the migration.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}
	}

	/**
	 * Complete a migration.
	 *
	 * Finalizes the migration, saves results, and adds setup wizard context.
	 *
	 * @since 1.3.1
	 *
	 * @param string $source_id Source adapter ID.
	 *
	 * @return array<string, mixed>|WP_Error Migration report or error.
	 */
	public function complete( string $source_id ) {

		$state = $this->service->get_state();

		if ( ! $state ) {
			return new WP_Error(
				'no_state',
				esc_html__( 'No migration state found.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		// Validate request source matches state source.
		if ( $source_id !== $state['source_id'] ) {
			return new WP_Error(
				'source_mismatch',
				esc_html__( 'Migration source mismatch.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		// Only allow completing a running, completed, or failed migration.
		if ( ! in_array( $state['status'], [ 'running', 'completed', 'failed' ], true ) ) {
			return new WP_Error(
				'invalid_state',
				esc_html__( 'Migration cannot be completed in its current state.', 'sugar-calendar-bookings-scheduling-appointments-lite' )
			);
		}

		// Complete the migration (saves state and metadata).
		$state = $this->service->complete_migration( $state );

		// Add setup_wizard context to the saved migration metadata.
		$this->add_migration_context( $source_id );

		return $this->build_completion_report( $state );
	}

	/**
	 * Build progress response data for the wizard.
	 *
	 * @since 1.3.1
	 *
	 * @param array<string, mixed> $state          Migration state.
	 * @param bool                 $is_complete    Whether migration is complete.
	 * @param string|null          $current_entity Current entity being processed.
	 *
	 * @return array<string, mixed>
	 */
	private function build_progress_response( array $state, bool $is_complete, ?string $current_entity = null ): array {

		$progress    = $this->service->calculate_progress( $state['total_processed'], $state['total_records'] );
		$next_entity = null;

		if ( ! $is_complete && isset( $state['entity_order'][ $state['current_entity'] ] ) ) {
			$next_entity = $state['entity_order'][ $state['current_entity'] ];
		}

		return [
			'done'            => $is_complete,
			'status'          => $is_complete ? 'completed' : 'running',
			'current_entity'  => $current_entity,
			'next_entity'     => $next_entity,
			'progress'        => $is_complete ? 100 : $progress,
			'total_processed' => $state['total_processed'],
			'total_migrated'  => $state['total_migrated'],
			'total_skipped'   => $state['total_skipped'],
			'total_failed'    => $state['total_failed'],
			'entities'        => $state['entities'],
		];
	}

	/**
	 * Build completion report.
	 *
	 * @since 1.3.1
	 *
	 * @param array<string, mixed> $state Migration state.
	 *
	 * @return array<string, mixed>
	 */
	private function build_completion_report( array $state ): array {

		return [
			'report' => [
				'status'         => $this->service->determine_result_status( $state ),
				'total_migrated' => $state['total_migrated'],
				'total_skipped'  => $state['total_skipped'],
				'total_failed'   => $state['total_failed'],
				'entities'       => $this->service->build_entity_report( $state ),
			],
		];
	}

	/**
	 * Build failure response data for the wizard.
	 *
	 * Reads the current (failed) state and returns a structured response
	 * with `done: true` and `status: failed`, including partial stats.
	 * This mirrors how the AJAX MigrationController communicates failures inline.
	 *
	 * @since 1.3.1
	 *
	 * @param string $error_message Human-readable error message.
	 *
	 * @return array<string, mixed>
	 */
	private function build_failure_response( string $error_message ): array {

		$state = $this->service->get_state();

		return [
			'done'            => true,
			'status'          => 'failed',
			'error_message'   => $error_message,
			'progress'        => is_array( $state ) ? $this->service->calculate_progress( $state['total_processed'], $state['total_records'] ) : 0,
			'total_processed' => $state['total_processed'] ?? 0,
			'total_migrated'  => $state['total_migrated'] ?? 0,
			'total_skipped'   => $state['total_skipped'] ?? 0,
			'total_failed'    => $state['total_failed'] ?? 0,
			'entities'        => $state['entities'] ?? [],
		];
	}

	/**
	 * Validate entity dependencies.
	 *
	 * Checks that required entities are included when dependent entities are selected.
	 * Dependencies are derived from the UI config's advanced options `requires` metadata.
	 *
	 * @since 1.3.1
	 *
	 * @param array<string> $entities  Selected entity types.
	 * @param string        $source_id Source adapter ID.
	 *
	 * @return true|WP_Error True if valid, WP_Error if validation fails.
	 */
	private function validate_entity_dependencies( array $entities, string $source_id ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$ui_config = $this->registry->get_ui_config( $source_id );

		if ( ! $ui_config ) {
			return true;
		}

		$modal_config = $ui_config->get_modal_config();
		$options      = $modal_config['options'] ?? [];

		// Find advanced options with `requires` metadata.
		$dependency_map = [];

		foreach ( $options as $option ) {
			if ( ! isset( $option['advanced_options'] ) ) {
				continue;
			}

			foreach ( $option['advanced_options'] as $advanced_option ) {
				if ( ! empty( $advanced_option['requires'] ) && ! empty( $advanced_option['entity_type'] ) ) {
					$dependency_map[ $advanced_option['entity_type'] ] = $advanced_option['requires'];
				}
			}
		}

		// Validate dependencies.
		foreach ( $entities as $entity ) {
			if ( ! isset( $dependency_map[ $entity ] ) ) {
				continue;
			}

			$missing = array_diff( $dependency_map[ $entity ], $entities );

			if ( ! empty( $missing ) ) {
				return new WP_Error(
					'dependency_violation',
					sprintf(
						/* translators: %1$s - entity type, %2$s - comma-separated list of required entities. */
						esc_html__( "Entity '%1\$s' requires: %2\$s", 'sugar-calendar-bookings-scheduling-appointments-lite' ),
						$entity,
						implode( ', ', $missing )
					)
				);
			}
		}

		return true;
	}

	/**
	 * Add setup_wizard context to migration metadata.
	 *
	 * Updates the saved migration data to include a `context` flag
	 * distinguishing wizard-initiated migrations from admin-initiated ones.
	 *
	 * @since 1.3.1
	 *
	 * @param string $source_id Source adapter ID.
	 */
	private function add_migration_context( string $source_id ): void {

		$options     = Plugin::get( Options::class );
		$import_data = $options->get( 'import' ) ?: [];
		$key         = $source_id . '_last_migrated';

		if ( ! isset( $import_data[ $key ] ) || ! is_array( $import_data[ $key ] ) ) {
			return;
		}

		$import_data[ $key ]['context'] = 'setup_wizard';

		$options->update(
			[
				'import' => $import_data,
			],
			false
		);
	}
}
