<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner\Queue\Handler;

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

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

/**
 * Executes one cloud upload batch.
 */
class UploadBatchHandler {

	/**
	 * @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'] : [];

		try {
			$orchestrator = new ScanOrchestrator( $scan_id );
			$follow_ups = [];
			$uploaded_total = 0;
			$submitted_jobs = 0;
			$last_result = [
				'uploaded_count'           => 0,
				'job_id'                   => null,
				'has_more'                 => false,
				'next_available_at'        => time(),
				'payload'                  => $payload,
				'active_cloud_jobs'        => 0,
				'active_cloud_job_target'  => 1,
			];
			$runtime = WorkerRuntime::current( $scan_id );
			$max_submissions = $this->get_max_submissions_per_task( $runtime );
			$deadline = microtime( true ) + $this->get_upload_time_budget_seconds( $runtime );

			while ( $submitted_jobs < $max_submissions && microtime( true ) < $deadline ) {
				$result = $orchestrator->run_upload_batch_task( $payload, $submitted_jobs > 0 );
				$last_result = array_merge( $last_result, $result );

				if ( ! empty( $result['scan_aborted'] ) ) {
					return TaskResult::completed(
						[
							'status'         => 'aborted',
							'uploaded_count' => $uploaded_total,
							'job_id'         => null,
							'job_count'      => $submitted_jobs,
						]
					);
				}

				if ( empty( $result['job_id'] ) ) {
					break;
				}

				$uploaded_total += max( 0, (int) ( $result['uploaded_count'] ?? 0 ) );
				$submitted_jobs++;

				$payload = is_array( $result['payload'] ?? null ) ? $result['payload'] : $orchestrator->get_upload_batch_payload( $payload );
				$active = max( 0, (int) ( $result['active_cloud_jobs'] ?? 0 ) );
				$target = max( 1, (int) ( $result['active_cloud_job_target'] ?? 1 ) );

				if ( empty( $result['has_more'] ) || $active >= $target ) {
					break;
				}
			}

			if ( $submitted_jobs > 0 ) {
				$this->log_upload_submission( $scan_id, $uploaded_total, $submitted_jobs, $last_result );
				$follow_ups[] = [
					'task_type'    => 'poll_cloud',
					'payload'      => [
						'poll_count'  => 0,
					],
					'available_at' => time() + 10,
					'priority'     => 60,
					'dedupe_key'   => TaskResult::build_scan_poll_dedupe_key(),
				];
			}

			if ( ! empty( $last_result['has_more'] ) ) {
				$next_payload = is_array( $last_result['payload'] ?? null )
					? $last_result['payload']
					: $orchestrator->get_upload_batch_payload( $payload );
				$follow_ups[] = [
					'task_type'    => 'upload_batch',
					'payload'      => $next_payload,
					'available_at' => (int) ( $last_result['next_available_at'] ?? $orchestrator->get_next_upload_available_at( $next_payload ) ),
					'priority'     => $orchestrator->get_upload_task_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',
					'uploaded_count' => $uploaded_total,
					'job_count'      => $submitted_jobs,
					'job_id'         => $last_result['job_id'] ?? null,
				],
				$follow_ups
			);
		} catch ( \Throwable $e ) {
			return TaskResult::failed( sanitize_text_field( $e->getMessage() ) );
		}
	}

	private function get_max_submissions_per_task( WorkerRuntime $runtime ): int {
		if ( $runtime->get_tick_budget_seconds() <= 5 ) {
			return 1;
		}

		$cap = max( 1, (int) floor( $runtime->get_tick_budget_seconds() / 4 ) );
		if ( WorkerRuntime::TRANSPORT_DEGRADED === $runtime->get_transport() ) {
			return min( 3, $cap );
		}

		if ( WorkerRuntime::TRANSPORT_SCHEDULED === $runtime->get_transport() ) {
			return min( 4, $cap );
		}

		return min( 6, $cap );
	}

	private function get_upload_time_budget_seconds( WorkerRuntime $runtime ): float {
		return (float) max( 2, min( 10, $runtime->get_tick_budget_seconds() - 1 ) );
	}

	private function log_upload_submission( string $scan_id, int $uploaded, int $job_count, array $result ): void {
		if ( $uploaded <= 0 ) {
			return;
		}

		$active = max( 1, (int) ( $result['active_cloud_jobs'] ?? 1 ) );
		$target = max( 1, (int) ( $result['active_cloud_job_target'] ?? $active ) );
		if ( $job_count <= 1 && $active > 1 && $active < $target && 0 !== $active % 3 ) {
			return;
		}

		if ( ! class_exists( '\SiteFort\Modules\Scanner\Service\ScanLogger' ) ) {
			require_once dirname( dirname( __DIR__ ) ) . '/Service/ScanLogger.php';
		}

		$message = $job_count > 1
			? sprintf(
				'Submitted %d file(s) to cloud analysis across %d jobs. Cloud queue: %d/%d.',
				$uploaded,
				$job_count,
				$active,
				$target
			)
			: sprintf(
				'Submitted %d file(s) to cloud analysis. Cloud queue: %d/%d.',
				$uploaded,
				$active,
				$target
			);

		\SiteFort\Modules\Scanner\Service\ScanLogger::log_throttled_for_scan(
			$scan_id,
			'upload_submission',
			$message,
			5
		);
	}

}
