<?php

namespace Fotogenic_AI;

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

/**
 * Store Queue application service. Preparation and Review are non-billable.
 */
final class Service_Store_Queue {
	private Store_Queue_Repo $repo;
	private Store_Queue_Snapshot_Builder $builder;
	private Store_Queue_Preflight $preflight;
	private Workflow_Draft_Normalizer $normalizer;
	private Service_Bulk $bulk;

	public function __construct(
		?Store_Queue_Repo $repo = null,
		?Store_Queue_Snapshot_Builder $builder = null,
		?Store_Queue_Preflight $preflight = null,
		?Workflow_Draft_Normalizer $normalizer = null,
		?Service_Bulk $bulk = null
	) {
		$this->repo       = $repo ?? Store_Queue_Repo::instance();
		$this->builder    = $builder ?? new Store_Queue_Snapshot_Builder();
		$this->preflight  = $preflight ?? new Store_Queue_Preflight();
		$this->normalizer = $normalizer ?? new Workflow_Draft_Normalizer();
		$this->bulk       = $bulk ?? new Service_Bulk();
	}

	/** @return array|\WP_Error */
	public function snapshot( $payload, int $user_id ) {
		if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'upload_files' ) || $user_id !== get_current_user_id() ) {
			return Messages::error( 'forbidden', 403 );
		}
		if ( $payload instanceof \stdClass ) {
			$payload = get_object_vars( $payload );
		}
		if ( ! is_array( $payload ) || ! isset( $payload['queueUuid'], $payload['draft'], $payload['selection'] ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$queue_uuid = strtolower( trim( (string) $payload['queueUuid'] ) );
		if ( ! wp_is_uuid( $queue_uuid ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$selection = $this->normalize_selection( $payload['selection'] );
		if ( is_wp_error( $selection ) ) {
			return $selection;
		}
		$raw_draft = $this->queue_draft( $payload['draft'], $selection );
		if ( is_wp_error( $raw_draft ) ) {
			return $raw_draft;
		}
		$draft = $this->normalizer->normalize( $raw_draft );
		if ( is_wp_error( $draft ) ) {
			return $draft;
		}
		if ( 'product_images' !== $draft['workflow'] || 'product' !== $draft['sourceType'] || 'bulk' !== $draft['scope'] ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_product_generate_required', __( 'Store Queue currently supports Product Generate Images only.', 'fotogenic-ai' ), [ 'status' => 400 ] );
		}
		$fingerprint = $this->normalizer->fingerprint( [ 'schemaVersion' => 1, 'draft' => $draft, 'selection' => $selection ] );
		if ( is_wp_error( $fingerprint ) ) {
			return $fingerprint;
		}
		$admission = $this->repo->admit_queue(
			[
				'queue_uuid'          => $queue_uuid,
				'user_id'             => $user_id,
				'request_fingerprint' => $fingerprint,
				'status'              => 'preparing',
				'history_label'       => $this->history_label( $selection ),
				'history_search_text' => implode( ' ', (array) ( $selection['categories'] ?? [] ) ),
				'selection_snapshot'  => $selection,
				'draft_snapshot'      => $draft,
			]
		);
		if ( true === ( $admission['conflict'] ?? false ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_conflict', __( 'This Product queue identity belongs to a different request.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		if ( true !== ( $admission['ok'] ?? false ) || ! is_array( $admission['queue'] ?? null ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_unavailable', __( 'The Product queue could not be prepared.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}

		$queue = $admission['queue'];
		if ( 'preparing' === (string) $queue['status'] ) {
			$prepared = $this->builder->prepare_tick( $queue_uuid, $user_id );
			if ( is_wp_error( $prepared ) ) {
				return $prepared;
			}
			$queue = $prepared;
			if ( 'preparing' === (string) ( $queue['status'] ?? '' ) && ! Store_Queue_Scheduler::instance()->schedule_tick( $queue_uuid, $user_id, 2 ) ) {
				return new \WP_Error( 'fotogenic_ai_store_queue_scheduler_unavailable', __( 'The Product queue could not continue because WordPress scheduling is unavailable.', 'fotogenic-ai' ), [ 'status' => 503 ] );
			}
		}
		$review = null;
		if ( 'reviewing' === (string) ( $queue['status'] ?? '' ) || 'ready' === (string) ( $queue['status'] ?? '' ) ) {
			$review = $this->preflight->review( $queue, $user_id );
			if ( is_wp_error( $review ) ) {
				return $review;
			}
			$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id ) ?? $queue;
		}

		return [
			'queue'    => $this->project_queue( $queue ),
			'counts'   => $this->repo->aggregate_counts( (int) $queue['id'], $user_id ),
			'review'   => $review,
			'page'     => $this->repo->page_items( (int) $queue['id'], $user_id, 1, 24 ),
		];
	}

	/** @return array|\WP_Error */
	public function prepare_tick( string $queue_uuid, int $user_id ) {
		return $this->builder->prepare_tick( $queue_uuid, $user_id );
	}

	/** @return array|\WP_Error */
	public function review( string $queue_uuid, int $user_id, $accepted_authority = null ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		return is_array( $queue )
			? $this->preflight->review( $queue, $user_id, $accepted_authority )
			: new \WP_Error( 'fotogenic_ai_store_queue_not_found', __( 'The Product queue could not be found.', 'fotogenic-ai' ), [ 'status' => 404 ] );
	}

	/** @return array|\WP_Error */
	public function start( string $queue_uuid, int $user_id, $accepted_authority, bool $allow_partial_balance = false ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		if ( ! is_array( $queue ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_not_found', __( 'The Product queue could not be found.', 'fotogenic-ai' ), [ 'status' => 404 ] );
		}
		if ( 'running' === (string) $queue['status'] ) {
			return $this->project_queue( $queue );
		}
		if ( 'ready' !== (string) $queue['status'] ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'Review this Product queue before starting it.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		$review = $this->preflight->review( $queue, $user_id, $accepted_authority );
		if ( is_wp_error( $review ) ) {
			return $review;
		}
		$selected_count  = max( 1, (int) $review['selectedCount'] );
		$credits_per_item = (float) $review['totalCredits'] / $selected_count;
		if ( (float) $review['balance'] < $credits_per_item ) {
			return new \WP_Error( 'fotogenic_ai_insufficient_credits', __( 'There are not enough credits to start one Product.', 'fotogenic-ai' ), [ 'status' => 402 ] );
		}
		if ( (float) $review['balanceAfter'] < 0 && ! $allow_partial_balance ) {
			return new \WP_Error(
				'fotogenic_ai_store_queue_partial_balance_confirmation',
				__( 'The current balance covers only part of this queue. Confirm that the queue may pause when credits run out.', 'fotogenic-ai' ),
				[ 'status' => 409, 'state' => 'partial_balance_confirmation' ]
			);
		}
		$evidence = (array) $review['authorityEvidence'];
		$consent  = [
			'schemaVersion'           => 1,
			'acceptedAt'              => time(),
			'queueUuid'               => $queue_uuid,
			'revision'                => (int) $queue['revision'] + 1,
			'snapshotFingerprint'     => (string) $review['snapshotFingerprint'],
			'contextFingerprint'      => (string) ( $evidence['contextFingerprint'] ?? '' ),
			'configurationFingerprint'=> (string) ( $evidence['configurationFingerprint'] ?? '' ),
			'pricingFingerprint'      => (string) ( $evidence['pricingFingerprint'] ?? '' ),
			'pricing'                 => (array) $review['pricing'],
			'creditsPerItem'          => $credits_per_item,
			'authorizedCredits'       => (float) $review['totalCredits'],
			'allowPartialBalance'     => $allow_partial_balance,
		];
		if ( ! $this->repo->update_queue_cas(
			(int) $queue['id'],
			$user_id,
			(int) $queue['revision'],
			[ 'status' => 'running', 'status_reason' => '', 'consent_snapshot' => $consent, 'started_at' => current_time( 'mysql', true ) ],
			true
		) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'The Product queue changed while it was starting.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id ) ?? $queue;
		if ( ! Store_Queue_Scheduler::instance()->schedule_tick( $queue_uuid, $user_id, 1 ) ) {
			$this->repo->update_queue_cas( (int) $queue['id'], $user_id, (int) $queue['revision'], [ 'status' => 'paused', 'status_reason' => 'scheduler_unavailable' ] );
			return new \WP_Error( 'fotogenic_ai_store_queue_scheduler_unavailable', __( 'The Product queue could not start because WordPress scheduling is unavailable.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}
		return $this->project_queue( $queue );
	}

	/** @return array|\WP_Error */
	public function pause( string $queue_uuid, int $user_id ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		if ( ! is_array( $queue ) ) {
			return Messages::error( 'not_found', 404 );
		}
		if ( 'paused' === (string) $queue['status'] ) {
			return $this->project_queue( $queue );
		}
		if ( 'running' !== (string) $queue['status'] || ! $this->repo->update_queue_cas( (int) $queue['id'], $user_id, (int) $queue['revision'], [ 'status' => 'paused', 'status_reason' => 'user_pause' ], true ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'This Product queue cannot be paused from its current state.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		$batch = $this->repo->get_current_batch( (int) $queue['id'] );
		if ( is_array( $batch ) ) {
			$this->bulk_control( 'pause', (string) $batch['bulk_run_uuid'], $user_id );
		}
		return $this->project_queue( $this->repo->get_queue_by_uuid( $queue_uuid, $user_id ) ?? $queue );
	}

	/** @return array|\WP_Error */
	public function resume( string $queue_uuid, int $user_id ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		if ( ! is_array( $queue ) ) {
			return Messages::error( 'not_found', 404 );
		}
		$reason = (string) ( $queue['status_reason'] ?? '' );
		if ( 'paused' !== (string) $queue['status'] || ! in_array( $reason, [ 'user_pause', 'insufficient_credits', 'scheduler_unavailable', 'credits_unavailable' ], true ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_review_required', __( 'Review this Product queue before resuming it.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		if ( 'insufficient_credits' === $reason ) {
			$consent = $this->decode_json( $queue['consent_snapshot'] ?? '' );
			$credits = Credits_Service::instance()->refresh( 0 );
			if ( is_wp_error( $credits ) || (float) ( $credits['balance'] ?? 0 ) < (float) ( $consent['creditsPerItem'] ?? INF ) ) {
				return new \WP_Error( 'fotogenic_ai_insufficient_credits', __( 'Add enough credits for at least one Product before resuming.', 'fotogenic-ai' ), [ 'status' => 402 ] );
			}
		}
		if ( ! $this->repo->update_queue_cas( (int) $queue['id'], $user_id, (int) $queue['revision'], [ 'status' => 'running', 'status_reason' => '' ], true ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'The Product queue changed while it was resuming.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		$batch = $this->repo->get_current_batch( (int) $queue['id'] );
		if ( is_array( $batch ) ) {
			$this->bulk_control( 'resume', (string) $batch['bulk_run_uuid'], $user_id );
		}
		if ( ! Store_Queue_Scheduler::instance()->schedule_tick( $queue_uuid, $user_id, 1 ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_scheduler_unavailable', __( 'The Product queue could not schedule its next step.', 'fotogenic-ai' ), [ 'status' => 503 ] );
		}
		return $this->project_queue( $this->repo->get_queue_by_uuid( $queue_uuid, $user_id ) ?? $queue );
	}

	/** @return array|\WP_Error */
	public function stop( string $queue_uuid, int $user_id ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		if ( ! is_array( $queue ) ) {
			return Messages::error( 'not_found', 404 );
		}
		if ( ! in_array( (string) $queue['status'], [ 'running', 'paused', 'stopping' ], true ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'This Product queue cannot be stopped from its current state.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		if ( 'stopping' !== (string) $queue['status'] && ! $this->repo->update_queue_cas( (int) $queue['id'], $user_id, (int) $queue['revision'], [ 'status' => 'stopping', 'status_reason' => 'user_stop' ], true ) ) {
			return new \WP_Error( 'fotogenic_ai_store_queue_state_conflict', __( 'The Product queue changed while it was stopping.', 'fotogenic-ai' ), [ 'status' => 409 ] );
		}
		$this->repo->skip_unadmitted_items( (int) $queue['id'] );
		$batch = $this->repo->get_current_batch( (int) $queue['id'] );
		if ( is_array( $batch ) ) {
			$this->bulk_control( 'stop', (string) $batch['bulk_run_uuid'], $user_id );
		}
		Store_Queue_Scheduler::instance()->schedule_tick( $queue_uuid, $user_id, 1 );
		return $this->project_queue( $this->repo->get_queue_by_uuid( $queue_uuid, $user_id ) ?? $queue );
	}

	/** @return array|\WP_Error */
	public function progress( string $queue_uuid, int $user_id, int $page = 1, int $per_page = 24, string $status = '' ) {
		$queue = $this->repo->get_queue_by_uuid( $queue_uuid, $user_id );
		if ( ! is_array( $queue ) ) {
			return Messages::error( 'not_found', 404 );
		}
		$items = $this->repo->page_items( (int) $queue['id'], $user_id, $page, $per_page, $status );
		$items['items'] = array_map( [ $this, 'project_item' ], $items['items'] );
		$batch = $this->repo->get_current_batch( (int) $queue['id'] ) ?? $this->repo->get_latest_batch( (int) $queue['id'] );
		$child = null;
		if ( is_array( $batch ) ) {
			$child_response = ( new Service_Bulk() )->progress( (string) $batch['bulk_run_uuid'], $user_id );
			if ( ! is_wp_error( $child_response ) && $child_response instanceof \WP_REST_Response ) {
				$child = $child_response->get_data();
			}
		}
		return [
			'queue'        => $this->project_queue( $queue ),
			'counts'       => $this->repo->aggregate_counts( (int) $queue['id'], $user_id ),
			'currentChild' => $child,
			'items'        => $items,
			'controls'     => $this->controls( $queue ),
		];
	}

	/** @return array{queue:array|null} */
	public function active( int $user_id ): array {
		$queue = $this->repo->get_active_queue_for_user( $user_id );
		return [ 'queue' => is_array( $queue ) ? $this->project_queue( $queue ) : null ];
	}

	private function bulk_control( string $action, string $run_uuid, int $user_id ): void {
		if ( ! wp_is_uuid( $run_uuid ) ) {
			return;
		}
		if ( 'pause' === $action ) {
			$this->bulk->pause( $run_uuid, $user_id );
		} elseif ( 'resume' === $action ) {
			$this->bulk->resume( $run_uuid, $user_id );
		} elseif ( 'stop' === $action ) {
			$this->bulk->stop( $run_uuid, $user_id );
		}
	}

	/** @return array<string, mixed> */
	private function project_item( array $item ): array {
		$summary = $this->decode_json( $item['summary_snapshot'] ?? '' );
		return [
			'id'           => (int) $item['id'],
			'ordinal'      => (int) $item['item_ordinal'],
			'subjectType'  => (string) $item['subject_type'],
			'subjectId'    => (int) $item['subject_id'],
			'label'        => fotogenic_ai_plain_text_label( (string) ( $summary['label'] ?? $item['search_label'] ?? '' ) ),
			'sku'          => sanitize_text_field( (string) ( $summary['sku'] ?? $item['sku'] ?? '' ) ),
			'imageUrl'     => esc_url_raw( (string) ( $summary['imageUrl'] ?? '' ) ),
			'status'       => sanitize_key( (string) $item['status'] ),
			'statusReason' => sanitize_key( (string) ( $item['status_reason'] ?? '' ) ),
			'updatedAt'    => (string) ( $item['updated_at'] ?? '' ),
		];
	}

	/** @return array{canPause:bool,canResume:bool,canStop:bool,requiresReview:bool} */
	private function controls( array $queue ): array {
		$status = (string) $queue['status'];
		$reason = (string) ( $queue['status_reason'] ?? '' );
		return [
			'canPause'      => 'running' === $status,
			'canResume'     => 'paused' === $status && in_array( $reason, [ 'user_pause', 'insufficient_credits', 'scheduler_unavailable', 'credits_unavailable' ], true ),
			'canStop'       => in_array( $status, [ 'running', 'paused' ], true ),
			'requiresReview'=> 'paused' === $status && 'review_required' === $reason,
		];
	}

	/** @return array<string, mixed> */
	private function decode_json( $value ): array {
		$decoded = is_string( $value ) ? json_decode( $value, true ) : null;
		return is_array( $decoded ) ? $decoded : [];
	}

	/** @return array|\WP_Error */
	private function normalize_selection( $raw ) {
		if ( $raw instanceof \stdClass ) {
			$raw = get_object_vars( $raw );
		}
		if ( ! is_array( $raw ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$strategy = sanitize_key( (string) ( $raw['strategy'] ?? '' ) );
		if ( ! in_array( $strategy, [ 'all', 'query', 'categories', 'explicit' ], true ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$selected = array_values( array_unique( array_filter( array_map( 'absint', (array) ( $raw['selectedIds'] ?? [] ) ) ) ) );
		$excluded = array_values( array_unique( array_filter( array_map( 'absint', (array) ( $raw['excludedIds'] ?? [] ) ) ) ) );
		if ( ! empty( array_intersect( $selected, $excluded ) ) || ( 'explicit' === $strategy && empty( $selected ) ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$categories = $this->normalize_product_categories( (array) ( $raw['categories'] ?? [] ) );
		$stock      = array_values( array_unique( array_intersect( array_map( 'sanitize_key', (array) ( $raw['stock'] ?? [] ) ), [ 'instock', 'outofstock', 'onbackorder' ] ) ) );
		if ( 'categories' === $strategy && empty( $categories ) ) {
			return Messages::error( 'invalid_request', 400 );
		}
		return [
			'strategy'    => $strategy,
			'search'      => substr( sanitize_text_field( (string) ( $raw['search'] ?? '' ) ), 0, 200 ),
			'categories'  => array_slice( $categories, 0, 50 ),
			'stock'       => $stock,
			'selectedIds' => $selected,
			'excludedIds' => $excluded,
		];
	}

	/** @return string[] */
	private function normalize_product_categories( array $values ): array {
		$categories = [];
		foreach ( $values as $value ) {
			$raw = trim( (string) $value );
			if ( '' === $raw ) {
				continue;
			}
			if ( ctype_digit( $raw ) ) {
				$term = get_term( absint( $raw ), 'product_cat' );
				if ( ! $term instanceof \WP_Term || is_wp_error( $term ) ) {
					continue;
				}
				$raw = (string) $term->slug;
			}
			$slug = sanitize_title( $raw );
			if ( '' !== $slug ) {
				$categories[] = $slug;
			}
		}
		return array_slice( array_values( array_unique( $categories ) ), 0, 50 );
	}

	/** @return \stdClass|\WP_Error */
	private function queue_draft( $raw_draft, array $selection ) {
		if ( $raw_draft instanceof \stdClass ) {
			$draft = clone $raw_draft;
		} elseif ( is_array( $raw_draft ) ) {
			$draft = json_decode( (string) wp_json_encode( $raw_draft ) );
		} else {
			return Messages::error( 'invalid_request', 400 );
		}
		if ( ! $draft instanceof \stdClass ) {
			return Messages::error( 'invalid_request', 400 );
		}
		$is_explicit     = 'explicit' === $selection['strategy'];
		$draft->scope   = 'bulk';
		$draft->selection = (object) [
			'strategy'    => $is_explicit ? 'explicit' : 'query',
			'selectedIds' => $selection['selectedIds'],
			'excludedIds' => $is_explicit ? [] : $selection['excludedIds'],
			'query'       => $is_explicit ? (object) [] : (object) [
				'search'     => 'all' === $selection['strategy'] ? '' : $selection['search'],
				'categories' => in_array( $selection['strategy'], [ 'query', 'categories' ], true ) ? $selection['categories'] : [],
				'stock'      => 'query' === $selection['strategy'] ? $selection['stock'] : [],
			],
		];
		return $draft;
	}

	/** @return array<string, mixed> */
	private function project_queue( array $queue ): array {
		return [
			'queueUuid'       => (string) $queue['queue_uuid'],
			'status'          => (string) $queue['status'],
			'statusReason'    => (string) ( $queue['status_reason'] ?? '' ),
			'revision'        => (int) $queue['revision'],
			'matchedCount'    => (int) $queue['matched_count'],
			'selectedCount'   => (int) $queue['selected_count'],
			'ineligibleCount' => (int) $queue['ineligible_count'],
			'expectedOutputCount' => (int) $queue['expected_output_count'],
			'estimatedCredits'=> (float) $queue['estimated_credits'],
			'completedCount'  => (int) $queue['completed_count'],
			'partialCount'    => (int) $queue['partial_count'],
			'failedCount'     => (int) $queue['failed_count'],
			'skippedCount'    => (int) $queue['skipped_count'],
			'attentionCount'  => (int) $queue['attention_count'],
			'label'           => fotogenic_ai_plain_text_label( (string) ( $queue['history_label'] ?? '' ) ),
			'scanAfterId'     => (int) $queue['scan_after_id'],
			'scanHighWaterId' => (int) $queue['scan_high_water_id'],
		];
	}

	private function history_label( array $selection ): string {
		$strategy = (string) $selection['strategy'];
		if ( 'categories' === $strategy ) {
			/* translators: %d: selected Product category count. */
			return sprintf( __( 'Generate Images · %d categories', 'fotogenic-ai' ), count( $selection['categories'] ) );
		}
		if ( 'explicit' === $strategy ) {
			/* translators: %d: selected Product count. */
			return sprintf( __( 'Generate Images · %d Products', 'fotogenic-ai' ), count( $selection['selectedIds'] ) );
		}
		return 'all' === $strategy ? __( 'Generate Images · All Products', 'fotogenic-ai' ) : __( 'Generate Images · Matching Products', 'fotogenic-ai' );
	}
}
