<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner\Queue\Handler;

use SiteFort\Modules\Scanner\Queue\ScanOrchestrator;
use SiteFort\Modules\Scanner\Queue\ScanQueue;

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

/**
 * Polls cloud jobs for analysis results.
 */
class PollCloudHandler {

	private const MAX_POLL_COUNT = 50;

	/**
	 * @param array<string,mixed> $task
	 * @param string              $scan_id
	 * @return TaskResult
	 */
	public function handle( array $task, string $scan_id ): TaskResult {
		$queue = ScanQueue::get_job( $scan_id );
		if ( ! is_array( $queue ) || ! in_array( (string) ( $queue['status'] ?? '' ), [ 'pending', 'running' ], true ) ) {
			return TaskResult::completed( [ 'status' => 'idle' ] );
		}

		$payload = is_array( $task['payload'] ?? null ) ? $task['payload'] : [];
		$job_ids = is_array( $payload['job_ids'] ?? null ) ? $payload['job_ids'] : [];
		$poll_count = max( 0, (int) ( $payload['poll_count'] ?? 0 ) );

		if ( ! empty( $job_ids ) && $poll_count >= self::MAX_POLL_COUNT ) {
			return TaskResult::failed(
				sprintf( 'Cloud job(s) did not complete after %d poll attempts', $poll_count )
			);
		}

		try {
			$orchestrator = new ScanOrchestrator( $scan_id );
			$result = empty( $job_ids ) ? $orchestrator->poll_active_jobs() : $orchestrator->poll_job_ids( $job_ids );
			if ( is_wp_error( $result ) ) {
				if ( $this->is_rate_limited_error( $result ) ) {
					$delay_seconds = $this->get_retry_delay( $result, $poll_count );
					$follow_ups = [
						[
							'task_type'    => 'poll_cloud',
							'payload'      => [
								'poll_count' => $poll_count,
							],
							'available_at' => time() + $delay_seconds,
							'priority'     => 60,
							'dedupe_key'   => TaskResult::build_scan_poll_dedupe_key(),
						],
					];

					return TaskResult::completed(
						[
							'status'      => 'deferred',
							'retry_after' => $delay_seconds,
						],
						$follow_ups
					);
				}

				return TaskResult::failed( sanitize_text_field( $result->get_error_message() ) );
			}

			$follow_ups = [];
			$pending_job_ids = is_array( $result['pending_job_ids'] ?? null ) ? $result['pending_job_ids'] : [];

			if ( ! empty( $pending_job_ids ) ) {
				$next_poll_count = $poll_count + 1;
				if ( empty( $job_ids ) ) {
					$next_poll_count = $this->normalize_scan_poll_count( $next_poll_count, $result );
				}
				$delay_seconds   = $this->get_poll_delay( $next_poll_count );
				if ( ! class_exists( '\SiteFort\Modules\Scanner\Service\ScanLogger' ) ) {
					require_once dirname( dirname( __DIR__ ) ) . '/Service/ScanLogger.php';
				}
				\SiteFort\Modules\Scanner\Service\ScanLogger::log_throttled_for_scan(
					$scan_id,
					'cloud_poll_pending',
					sprintf(
						'Cloud analysis still pending for %d job(s); next poll in %d seconds.',
						count( $pending_job_ids ),
						$delay_seconds
					),
					5,
					1,
					'info'
				);
				$follow_ups[] = [
					'task_type'    => 'poll_cloud',
					'payload'      => [
						'poll_count' => $next_poll_count,
					],
					'available_at' => time() + $delay_seconds,
					'priority'     => 60,
					'dedupe_key'   => TaskResult::build_scan_poll_dedupe_key(),
				];
			} else {
				$resolved_count = count( is_array( $result['completed_job_ids'] ?? null ) ? $result['completed_job_ids'] : [] )
					+ count( is_array( $result['failed_job_ids'] ?? null ) ? $result['failed_job_ids'] : [] );
				if ( $resolved_count <= 0 ) {
					$resolved_count = (int) ( $result['processed'] ?? 0 );
				}

				if ( $resolved_count > 0 ) {
					if ( ! class_exists( '\SiteFort\Modules\Scanner\Service\ScanLogger' ) ) {
						require_once dirname( dirname( __DIR__ ) ) . '/Service/ScanLogger.php';
					}
					\SiteFort\Modules\Scanner\Service\ScanLogger::log(
						sprintf(
							'Cloud results received for %d job(s).',
							$resolved_count
						),
						1,
						'info'
					);
				}
			}

			if ( ! empty( $result['requeued_upload'] ) ) {
				$upload_plan = $orchestrator->get_upload_task_plan();
				if ( ! empty( $upload_plan['has_candidates'] ) ) {
					$follow_ups[] = [
						'task_type'    => 'upload_batch',
						'payload'      => $upload_plan['payload'],
						'available_at' => $upload_plan['available_at'],
						'priority'     => $upload_plan['priority'],
						'dedupe_key'   => TaskResult::build_scan_upload_dedupe_key(),
					];
				}
			}

			if ( $orchestrator->has_file_worker_work() ) {
				$follow_ups[] = [
					'task_type'    => 'scan_worker',
					'payload'      => [],
					'available_at' => $orchestrator->get_next_file_worker_available_at(),
					'priority'     => 100,
				];
			}

			if ( $orchestrator->is_ready_for_finalize() ) {
				$follow_ups[] = [
					'task_type'    => 'finalize',
					'payload'      => [ 'scan_id' => $scan_id ],
					'available_at' => time(),
					'priority'     => 10,
					'dedupe_key'   => 'finalize',
				];
			}

			return TaskResult::completed(
				[
					'status'            => 'completed',
					'processed'         => (int) ( $result['processed'] ?? 0 ),
					'pending_job_ids'   => array_values( $pending_job_ids ),
					'completed_job_ids' => array_values( is_array( $result['completed_job_ids'] ?? null ) ? $result['completed_job_ids'] : [] ),
					'failed_job_ids'    => array_values( is_array( $result['failed_job_ids'] ?? null ) ? $result['failed_job_ids'] : [] ),
				],
				$follow_ups
			);
		} catch ( \Throwable $e ) {
			return TaskResult::failed( sanitize_text_field( $e->getMessage() ) );
		}
	}

	/**
	 * @param int $poll_count
	 * @return int
	 */
	private function get_poll_delay( int $poll_count ): int {
		if ( $poll_count <= 1 ) {
			return 10;
		} elseif ( $poll_count === 2 ) {
			return 10;
		} elseif ( $poll_count === 3 ) {
			return 20;
		} elseif ( $poll_count === 4 ) {
			return 60;
		} else {
			return 120;
		}
	}

	/**
	 * Keep scan-level polling responsive when a fresh job joins older pending jobs.
	 *
	 * @param int                 $next_poll_count
	 * @param array<string,mixed> $result
	 * @return int
	 */
	private function normalize_scan_poll_count( int $next_poll_count, array $result ): int {
		if ( ! isset( $result['youngest_pending_age'] ) || null === $result['youngest_pending_age'] ) {
			return $next_poll_count;
		}

		$youngest_pending_age = max( 0, (int) $result['youngest_pending_age'] );
		if ( $youngest_pending_age < 30 ) {
			return min( $next_poll_count, 1 );
		}

		if ( $youngest_pending_age < 90 ) {
			return min( $next_poll_count, 3 );
		}

		return $next_poll_count;
	}

	private function is_rate_limited_error( \WP_Error $error ): bool {
		$error_data = $this->get_error_data( $error );
		$status_code = (int) ( $error_data['status'] ?? 0 );

		return $error->get_error_code() === 'rate_limited'
			|| 429 === $status_code
			|| false !== strpos( $error->get_error_message(), '429' );
	}

	private function get_retry_delay( \WP_Error $error, int $poll_count ): int {
		$error_data = $this->get_error_data( $error );
		if ( isset( $error_data['retry_after'] ) ) {
			return max( 5, min( 120, absint( $error_data['retry_after'] ) ) );
		}

		$detail = is_array( $error_data['detail'] ?? null ) ? $error_data['detail'] : [];
		if ( isset( $detail['retry_after'] ) ) {
			return max( 5, min( 120, absint( $detail['retry_after'] ) ) );
		}

		return $this->get_poll_delay( $poll_count + 1 );
	}

	private function get_error_data( \WP_Error $error ): array {
		$error_data = $error->get_error_data( 'api_error' );
		if ( empty( $error_data ) ) {
			$error_data = $error->get_error_data();
		}

		return is_array( $error_data ) ? $error_data : [];
	}
}
