<?php
/**
 * Local CLI runner for Niwa background cron work.
 *
 * Intended server cron/scheduled task:
 * /opt/alt/php82/usr/bin/php wp-content/plugins/niwa-ai/bin/background-runner.php --limit=20
 *
 * @package Niwa_AI
 */

if ( 'cli' !== PHP_SAPI ) {
	exit( 1 );
}

define( 'NIWA_AI_CLI_RUNNER', true );
define( 'NIWA_AI_BACKGROUND_RUNNER', true );

$limit       = 8;
$loop        = false;
$sleep       = 60;
$max_runtime = 0;
$rotate      = false;
$clock_token = '';
$source      = 'cli';
foreach ( array_slice( $argv, 1 ) as $arg ) {
	if ( 0 === strpos( $arg, '--limit=' ) ) {
		$limit = max( 1, intval( substr( $arg, 8 ) ) );
	} elseif ( '--loop' === $arg || 0 === strpos( $arg, '--loop=' ) ) {
		$loop = true;
	} elseif ( 0 === strpos( $arg, '--sleep=' ) ) {
		$sleep = max( 15, intval( substr( $arg, 8 ) ) );
	} elseif ( 0 === strpos( $arg, '--max-runtime=' ) ) {
		$max_runtime = max( 0, intval( substr( $arg, 14 ) ) );
	} elseif ( '--rotate' === $arg || 0 === strpos( $arg, '--rotate=' ) ) {
		$rotate = true;
	} elseif ( 0 === strpos( $arg, '--clock-token=' ) ) {
		$clock_token = preg_replace( '/[^A-Za-z0-9_-]/', '', substr( $arg, 14 ) );
	} elseif ( 0 === strpos( $arg, '--source=' ) ) {
		$source = preg_replace( '/[^A-Za-z0-9_-]/', '', substr( $arg, 9 ) );
	}
}

if ( '' === $source ) {
	$source = $loop ? 'internal_clock' : 'cli';
}

if ( $loop && $max_runtime <= 0 ) {
	$max_runtime = 1800;
}

$dir          = __DIR__;
$wp_load_path = '';
for ( $i = 0; $i < 8; $i++ ) {
	$candidate = $dir . '/wp-load.php';
	if ( is_readable( $candidate ) ) {
		$wp_load_path = $candidate;
		break;
	}

	$parent = dirname( $dir );
	if ( $parent === $dir ) {
		break;
	}
	$dir = $parent;
}

if ( '' === $wp_load_path ) {
	fwrite( STDERR, "wp-load.php not found\n" );
	exit( 1 );
}

$_SERVER['REQUEST_METHOD']  = 'GET';
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['HTTP_HOST']       = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : 'localhost';
$_SERVER['REQUEST_URI']     = '/';

require_once $wp_load_path;

if ( ! class_exists( 'Niwa_AI_Cron_Maintenance' ) ) {
	fwrite( STDERR, "Niwa cron maintenance unavailable\n" );
	exit( 1 );
}

$niwa_cli_lock_handle = null;
if ( $loop ) {
	$lock_dir = defined( 'WP_CONTENT_DIR' ) ? trailingslashit( WP_CONTENT_DIR ) . 'uploads/niwa-ai/locks' : sys_get_temp_dir();
	if ( function_exists( 'wp_mkdir_p' ) ) {
		wp_mkdir_p( $lock_dir );
	}
	$lock_path = trailingslashit( $lock_dir ) . 'background-runner.lock';
	$niwa_cli_lock_handle = @fopen( $lock_path, 'c' );
	if ( is_resource( $niwa_cli_lock_handle ) && ! @flock( $niwa_cli_lock_handle, LOCK_EX | LOCK_NB ) ) {
		fwrite( STDOUT, wp_json_encode( array( 'success' => true, 'skipped' => 'background_runner_already_locked' ) ) . "\n" );
		exit( 0 );
	}
	if ( is_resource( $niwa_cli_lock_handle ) ) {
		register_shutdown_function(
			static function () use ( &$niwa_cli_lock_handle ) {
				if ( is_resource( $niwa_cli_lock_handle ) ) {
					@flock( $niwa_cli_lock_handle, LOCK_UN );
					@fclose( $niwa_cli_lock_handle );
				}
			}
		);
	}
}

if ( $loop && '' === $clock_token ) {
	$clock_token = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : bin2hex( random_bytes( 16 ) );
}

$started = time();
$last    = array(
	'success'   => true,
	'processed' => 0,
	'errors'    => array(),
);
$exit_reason = 'completed';

if ( $loop && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
	Niwa_AI_Admin_Autonomy_Background_Clock::record_start(
		$clock_token,
		function_exists( 'getmypid' ) ? getmypid() : 0,
		array(
			'source'              => $source,
			'sleep_seconds'       => $sleep,
			'limit'               => $limit,
			'max_runtime_seconds' => $max_runtime,
		)
	);
}

do {
	if ( $loop && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) && Niwa_AI_Admin_Autonomy_Background_Clock::should_stop( $clock_token ) ) {
		$exit_reason = 'token_replaced';
		break;
	}

	$last = Niwa_AI_Cron_Maintenance::run_due_managed_hooks( $limit, $source );
	if ( $loop && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
		Niwa_AI_Admin_Autonomy_Background_Clock::record_tick(
			$clock_token,
			function_exists( 'getmypid' ) ? getmypid() : 0,
			$last,
			array(
				'source'              => $source,
				'sleep_seconds'       => $sleep,
				'limit'               => $limit,
				'max_runtime_seconds' => $max_runtime,
			)
		);
		if ( Niwa_AI_Admin_Autonomy_Background_Clock::should_stop( $clock_token ) ) {
			$exit_reason = 'stop_requested';
			break;
		}
	}

	if ( ! $loop ) {
		break;
	}
	if ( $max_runtime > 0 && time() - $started >= max( 60, $max_runtime - max( $sleep + 10, 45 ) ) ) {
		if ( $rotate && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
			if ( is_resource( $niwa_cli_lock_handle ) ) {
				@flock( $niwa_cli_lock_handle, LOCK_UN );
				@fclose( $niwa_cli_lock_handle );
				$niwa_cli_lock_handle = null;
			}
			$rotation = Niwa_AI_Admin_Autonomy_Background_Clock::spawn_successor(
				$clock_token,
				array(
					'source'              => $source,
					'sleep_seconds'       => $sleep,
					'limit'               => $limit,
					'max_runtime_seconds' => $max_runtime,
				)
			);
			$exit_reason = ! empty( $rotation['success'] ) ? 'rotated' : 'rotation_failed';
		} else {
			$exit_reason = 'max_runtime';
		}
		break;
	}
	sleep( $sleep );
} while ( true );

if ( $loop && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
	Niwa_AI_Admin_Autonomy_Background_Clock::record_stop( $clock_token, $exit_reason );
}

if ( empty( $last['success'] ) ) {
	fwrite( STDERR, wp_json_encode( $last ) . "\n" );
	exit( 1 );
}

fwrite( STDOUT, wp_json_encode( $last ) . "\n" );
exit( 0 );
