<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner\Queue;

use SiteFort\Modules\Scanner\Service\ScannerSettings;

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

class WorkerRuntime {

	public const TRANSPORT_FAST_ACK_HTTP = 'fast_ack_http';
	public const TRANSPORT_SCHEDULED = 'scheduled_worker';
	public const TRANSPORT_DEGRADED = 'degraded_host';

	private const HTTP_DEGRADED_TTL = 900;
	private const ACTIVE_TTL = 90;
	// One missed ack must not pin the scan to degraded transport; require repeats.
	private const HTTP_FAILURE_STRIKES = 2;
	private const HTTP_FAILURE_WINDOW = 300;

	private string $transport;
	private int $execution_limit;

	private function __construct( string $transport, int $execution_limit ) {
		$this->transport = $transport;
		$this->execution_limit = $execution_limit;
	}

	public static function current( string $scan_id = '' ): self {
		$scan_id = sanitize_text_field( $scan_id );
		$transport = self::resolve_transport( $scan_id );

		return new self( $transport, self::resolve_execution_limit( $transport ) );
	}

	public static function mark_http_dispatch_failure( string $scan_id, string $reason = '' ): void {
		$scan_id = sanitize_text_field( $scan_id );
		if ( '' === $scan_id ) {
			return;
		}

		$strike_key = self::failure_strike_key( $scan_id );
		$strikes = (int) get_transient( $strike_key ) + 1;
		if ( $strikes < self::HTTP_FAILURE_STRIKES ) {
			set_transient( $strike_key, $strikes, self::HTTP_FAILURE_WINDOW );
			return;
		}

		delete_transient( $strike_key );
		set_transient(
			self::degraded_key( $scan_id ),
			[
				'reason'    => sanitize_key( $reason ),
				'failed_at' => time(),
			],
			self::HTTP_DEGRADED_TTL
		);
	}

	public static function clear_scan_state( string $scan_id ): void {
		$scan_id = sanitize_text_field( $scan_id );
		if ( '' === $scan_id ) {
			return;
		}

		delete_transient( self::degraded_key( $scan_id ) );
		delete_transient( self::active_key( $scan_id ) );
		delete_transient( self::failure_strike_key( $scan_id ) );
	}

	public static function mark_worker_active( string $scan_id ): void {
		$scan_id = sanitize_text_field( $scan_id );
		if ( '' === $scan_id ) {
			return;
		}

		set_transient( self::active_key( $scan_id ), time() + self::ACTIVE_TTL, self::ACTIVE_TTL );
	}

	public static function clear_worker_active( string $scan_id ): void {
		$scan_id = sanitize_text_field( $scan_id );
		if ( '' === $scan_id ) {
			return;
		}

		delete_transient( self::active_key( $scan_id ) );
	}

	public static function is_worker_active( string $scan_id ): bool {
		$scan_id = sanitize_text_field( $scan_id );
		if ( '' === $scan_id ) {
			return false;
		}

		return (int) get_transient( self::active_key( $scan_id ) ) > time();
	}

	public function get_transport(): string {
		return $this->transport;
	}

	public function uses_http_transport(): bool {
		return self::TRANSPORT_FAST_ACK_HTTP === $this->transport;
	}

	public function get_ack_timeout_seconds(): int {
		/**
		 * Loopback dispatch ack timeout in seconds, clamped to 2-15.
		 *
		 * @param int $timeout Default 5.
		 */
		$timeout = (int) apply_filters( 'sitefort_scanner_worker_ack_timeout', 5 );

		return max( 2, min( 15, $timeout ) );
	}

	public function get_tick_budget_seconds(): int {
		$safety_margin = min( 5, max( 1, (int) floor( $this->execution_limit * 0.2 ) ) );
		return max( 1, $this->execution_limit - $safety_margin );
	}

	public function get_file_slice_limit_seconds(): int {
		return max( 1, $this->execution_limit );
	}

	public function get_max_tasks_per_tick(): int {
		if ( $this->execution_limit <= 8 ) {
			return 1;
		}

		if ( self::TRANSPORT_DEGRADED === $this->transport ) {
			return 3;
		}

		return self::TRANSPORT_FAST_ACK_HTTP === $this->transport ? 8 : 6;
	}

	public function get_dispatch_interval_seconds(): int {
		if ( self::TRANSPORT_DEGRADED === $this->transport ) {
			return 5;
		}

		return self::TRANSPORT_FAST_ACK_HTTP === $this->transport ? 2 : 3;
	}

	private static function resolve_transport( string $scan_id ): string {
		if ( '' !== $scan_id && get_transient( self::degraded_key( $scan_id ) ) ) {
			return self::TRANSPORT_DEGRADED;
		}

		return self::supports_fast_ack() ? self::TRANSPORT_FAST_ACK_HTTP : self::TRANSPORT_SCHEDULED;
	}

	private static function supports_fast_ack(): bool {
		return function_exists( 'fastcgi_finish_request' ) || function_exists( 'litespeed_finish_request' );
	}

	private static function resolve_execution_limit( string $transport ): int {
		$limit = (int) ini_get( 'max_execution_time' );
		if ( $limit <= 0 ) {
			$limit = 30;
		}

		if ( ! class_exists( ScannerSettings::class ) ) {
			require_once dirname( __DIR__ ) . '/Service/ScannerSettings.php';
		}

		if ( class_exists( ScannerSettings::class ) ) {
			$settings = ScannerSettings::get_all();
			$user_limit = absint( $settings['stageMaxExecutionTime'] ?? 0 );
			if ( $user_limit > 0 ) {
				$limit = min( $limit, $user_limit );
			}
		}

		if ( self::TRANSPORT_DEGRADED === $transport ) {
			$cap = 18;
		} elseif ( self::TRANSPORT_FAST_ACK_HTTP === $transport ) {
			$cap = 45;
		} else {
			$cap = 25;
		}

		return max( 1, min( $limit, $cap ) );
	}

	private static function degraded_key( string $scan_id ): string {
		return 'sitefort_scanner_transport_degraded_' . substr( hash( 'sha256', $scan_id ), 0, 24 );
	}

	private static function failure_strike_key( string $scan_id ): string {
		return 'sitefort_scanner_dispatch_strikes_' . substr( hash( 'sha256', $scan_id ), 0, 24 );
	}

	private static function active_key( string $scan_id ): string {
		return 'sitefort_scanner_worker_active_' . substr( hash( 'sha256', $scan_id ), 0, 24 );
	}
}
