<?php

/**
 * REST API controller for polling async Plugin Builder job status.
 *
 * Exposes GET /lolacore/v1/builder/job/{job_id} so the frontend can
 * poll progress of background generation tasks.
 *
 * @package LolaCore\PluginBuilder\Async
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\PluginBuilder\Async;

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

class JobController {

	private const NAMESPACE  = 'lolacore/v1';
	private const ROUTE_BASE = '/builder/job/(?P<job_id>[a-f0-9-]+)';

	private JobManager $jobManager;

	public function __construct( ?JobManager $jobManager = null ) {
		$this->jobManager = $jobManager ?? new JobManager();
	}

	/**
	 * Register the REST route via rest_api_init.
	 */
	public function register(): void {
		\add_action( 'rest_api_init', array( $this, 'registerRoutes' ) );
	}

	/**
	 * Callback for rest_api_init — registers the route.
	 */
	public function registerRoutes(): void {
		$jobIdArg = array(
			'job_id' => array(
				'required'          => true,
				'validate_callback' => static function ( $value ): bool {
					return is_string( $value ) && preg_match( '/^[a-f0-9-]+$/', $value ) === 1;
				},
				'sanitize_callback' => 'sanitize_text_field',
			),
		);

		\register_rest_route( self::NAMESPACE, self::ROUTE_BASE, array(
			'methods'             => 'GET',
			'callback'            => array( $this, 'getJobStatus' ),
			'permission_callback' => static function (): bool {
				return \current_user_can( 'manage_options' );
			},
			'args'                => $jobIdArg,
		) );

		\register_rest_route( self::NAMESPACE, self::ROUTE_BASE . '/retry', array(
			'methods'             => 'POST',
			'callback'            => array( $this, 'retryJob' ),
			'permission_callback' => static function (): bool {
				return \current_user_can( 'manage_options' );
			},
			'args'                => $jobIdArg,
		) );
	}

	/**
	 * GET /lolacore/v1/builder/job/{job_id}
	 *
	 * @param \WP_REST_Request $request
	 * @return \WP_REST_Response
	 */
	public function getJobStatus( $request ) {
		$jobId = (string) $request->get_param( 'job_id' );
		$state = $this->jobManager->getState( $jobId );

		if ( $state === null ) {
			return new \WP_REST_Response(
				array(
					'status'  => 'not_found',
					'message' => __('Job not found or expired.', 'lolacore'),
				),
				404
			);
		}

		// Only expose the fields the frontend needs — never leak params.
		return new \WP_REST_Response(
			array(
				'status'      => $state['status'],
				'progress'    => $state['progress'],
				'step'        => $state['step'],
				'result'      => $state['result'],
				'error'       => $state['error'],
				'retry_count' => (int) ( $state['retry_count'] ?? 0 ),
				'max_retries' => JobManager::MAX_RETRIES,
			),
			200
		);
	}

	/**
	 * POST /lolacore/v1/builder/job/{job_id}/retry
	 *
	 * Re-runs a failed generation job. Reads the original action and parameters
	 * from the failed job's transient (server-side only — params are never sent
	 * to the client), creates a fresh job, and dispatches the background worker.
	 *
	 * @param \WP_REST_Request $request
	 * @return \WP_REST_Response
	 */
	public function retryJob( $request ) {
		$jobId = (string) $request->get_param( 'job_id' );
		$state = $this->jobManager->getState( $jobId );

		if ( $state === null ) {
			return new \WP_REST_Response(
				array(
					'status'  => 'not_found',
					'message' => __('Job not found or expired.', 'lolacore'),
				),
				404
			);
		}

		if ( ( $state['status'] ?? '' ) !== JobManager::STATUS_FAILED ) {
			return new \WP_REST_Response(
				array(
					'status'  => 'not_failed',
					'message' => __('Only a failed build can be retried.', 'lolacore'),
				),
				409
			);
		}

		$retryCount = (int) ( $state['retry_count'] ?? 0 );
		if ( $retryCount >= JobManager::MAX_RETRIES ) {
			return new \WP_REST_Response(
				array(
					'status'      => 'limit_reached',
					'message'     => __('This build has already been retried the maximum number of times.', 'lolacore'),
					'retry_count' => $retryCount,
					'max_retries' => JobManager::MAX_RETRIES,
				),
				429
			);
		}

		$newJobId    = \wp_generate_uuid4();
		$workerMode  = JobManager::WORKER_MODE_GENERATE;
		$retryContext = array();

		if ( JobManager::hasRetryArtifact( $state ) ) {
			$workerMode   = JobManager::WORKER_MODE_FIX_RETRY;
			$retryContext = array(
				'last_artifact'    => (array) $state['last_artifact'],
				'ledger_snapshot'  => is_array( $state['ledger_snapshot'] ?? null )
					? (array) $state['ledger_snapshot']
					: array(),
				'library_version'  => max( 1, (int) ( $state['library_version'] ?? 1 ) ),
			);
		}

		$this->jobManager->createJob(
			$newJobId,
			(string) ( $state['action'] ?? '' ),
			(array) ( $state['params'] ?? array() ),
			(int) ( $state['user_id'] ?? \get_current_user_id() ),
			$retryCount + 1,
			$workerMode,
			$retryContext
		);

		if ( ! BuilderWorker::dispatch( $newJobId ) ) {
			$this->jobManager->cleanup( $newJobId );
			return new \WP_REST_Response(
				array(
					'status'  => 'dispatch_failed',
					'message' => __('I could not start the build again right now.', 'lolacore'),
				),
				500
			);
		}

		return new \WP_REST_Response(
			array(
				'status'      => 'queued',
				'job_id'      => $newJobId,
				'retry_count' => $retryCount + 1,
				'max_retries' => JobManager::MAX_RETRIES,
			),
			200
		);
	}
}
