<?php

namespace AIPT\Api\Chatbot\Jobs;

use AIPT\Api\Chatbot\ChatbotLogger;
use AIPT\Api\Chatbot\Exceptions\AsyncUnavailableException;

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

class EnqueueService {

	private ChatbotJobRepository $repository;

	private $is_ready_callback;

	private $schedule_callback;

	public function __construct(
		?ChatbotJobRepository $repository = null,
		?callable $is_ready_callback = null,
		?callable $schedule_callback = null
	) {
		$this->repository        = $repository ?? new ChatbotJobRepository();
		$this->is_ready_callback = $is_ready_callback;
		$this->schedule_callback = $schedule_callback;
	}

	public function enqueue( string $session_id, string $client_request_id, array $payload ): array {
		if ( ! $this->isReady() ) {
			ChatbotLogger::warn(
				'enqueue_async_unavailable',
				[
					'session_id' => $session_id,
					'request_id' => $client_request_id,
				]
			);
			throw new AsyncUnavailableException();
		}

		$created   = $this->repository->createOrFindPendingJob( $session_id, $client_request_id, $payload );
		$job_uuid  = $created['job_uuid'];
		$was_fresh = (bool) $created['was_fresh'];
		$row       = $this->repository->findByUuid( $job_uuid );

		if ( null === $row ) {
			ChatbotLogger::warn(
				'enqueue_row_missing_after_create',
				[
					'job_uuid'   => $job_uuid,
					'session_id' => $session_id,
					'request_id' => $client_request_id,
				]
			);
			throw new AsyncUnavailableException();
		}

		$token = $this->getStatusToken( $row );

		if ( ! $was_fresh ) {
			$existing_status = isset( $row['status'] ) ? (string) $row['status'] : 'pending';
			ChatbotLogger::info(
				'enqueue_existing_job_returned',
				[
					'job_uuid'   => $job_uuid,
					'status'     => $existing_status,
					'session_id' => $session_id,
					'request_id' => $client_request_id,
				]
			);

			return [
				'job_uuid'     => $job_uuid,
				'status_token' => $token,
				'poll_url'     => \rest_url( 'aipt/v1/chatbot/status/' . rawurlencode( $job_uuid ) ),
			];
		}

		if ( ! $this->scheduleJob( $job_uuid ) ) {

			$this->repository->markScheduleFailed( $job_uuid, 'async_schedule_failed' );

			ChatbotLogger::warn(
				'enqueue_schedule_failed',
				[
					'job_uuid'   => $job_uuid,
					'session_id' => $session_id,
					'request_id' => $client_request_id,
				]
			);
			throw new AsyncUnavailableException();
		}

		ChatbotLogger::info(
			'enqueue_created',
			[
				'job_uuid'   => $job_uuid,
				'session_id' => $session_id,
				'request_id' => $client_request_id,
			]
		);

		return [
			'job_uuid'     => $job_uuid,
			'status_token' => $token,
			'poll_url'     => \rest_url( 'aipt/v1/chatbot/status/' . rawurlencode( $job_uuid ) ),
		];
	}

	private function isReady(): bool {
		if ( is_callable( $this->is_ready_callback ) ) {
			return (bool) call_user_func( $this->is_ready_callback );
		}

		return AsyncRunner::isReady();
	}

	private function scheduleJob( string $job_uuid ): bool {
		if ( is_callable( $this->schedule_callback ) ) {
			return (bool) call_user_func( $this->schedule_callback, $job_uuid );
		}

		return AsyncRunner::scheduleJob( $job_uuid );
	}

	private function getStatusToken( array $row ): string {
		if ( empty( $row['status_token'] ) || ! is_string( $row['status_token'] ) ) {

			throw new AsyncUnavailableException();
		}

		return $row['status_token'];
	}
}
