<?php
declare(strict_types=1);

/**
 * YachtMemory Stay Processing - scheduled runner, Phase 1D.2c v1.1.
 *
 * Operational wrapper around StayProcessingService.
 *
 * Responsibilities:
 * - shared non-blocking lock prevents concurrent APPLY processing
 * - stay_processing.enabled gates automatic processing
 * - cadence state reduces frequent systemd wake-ups to application cadence
 * - executed application cycles are logged as SCHEDULED runs
 *
 * Runtime files live in /run/yachtmemory. The directory is managed by
 * systemd-tmpfiles and shared by the scheduled and manual APPLY runners.
 *
 * Usage:
 *   php shared_dashboard/scripts/run_stay_processing_scheduled.php \
 *     --scheduled --vessel-id=1 [--limit=20]
 */

$appRoot = dirname(__DIR__, 2);
require_once $appRoot . '/bootstrap.php';

ym_require('src/services/SystemSettingsService.php');
ym_require('src/services/StayProcessingService.php');
ym_require('src/services/StayProcessingRunWriter.php');

$options = getopt('', ['scheduled', 'vessel-id:', 'limit::']);

$scheduled = array_key_exists('scheduled', $options);
$vesselId = isset($options['vessel-id']) ? (int)$options['vessel-id'] : 0;
$limit = isset($options['limit']) ? (int)$options['limit'] : 20;

if (!$scheduled || $vesselId <= 0 || $limit < 1) {
    fwrite(
        STDERR,
        "Usage: php shared_dashboard/scripts/run_stay_processing_scheduled.php "
        . "--scheduled --vessel-id=1 [--limit=20]\n"
    );
    exit(2);
}

$config = SystemSettingsService::getStayProcessingConfig();

$enabled = (bool)($config['enabled'] ?? false);

$workerIntervalMin = max(
    2,
    min(60, (int)($config['worker_interval_min'] ?? 5))
);

$retryIntervalMin = max(
    10,
    min(360, (int)($config['retry_interval_min'] ?? 30))
);

$runtimeDir = '/run/yachtmemory';
$lockFile = $runtimeDir . '/stay_processing.lock';
$stateFile = $runtimeDir . '/stay_processing.last_run';

if (!is_dir($runtimeDir)) {
    fwrite(
        STDERR,
        "STAY PROCESSING FAILED: runtime directory {$runtimeDir} does not exist.\n"
    );
    exit(1);
}

$lockHandle = fopen($lockFile, 'c');

if ($lockHandle === false) {
    fwrite(
        STDERR,
        "STAY PROCESSING FAILED: cannot open shared processing lock file.\n"
    );
    exit(1);
}

if (!flock($lockHandle, LOCK_EX | LOCK_NB)) {
    echo "YachtMemory Stay Processing - another APPLY run is active; exiting.\n";
    exit(0);
}

/*
 * Disabled means administratively stopped. No SKIPPED history row is created
 * for technical timer ticks.
 */
if (!$enabled) {
    echo "YachtMemory Stay Processing - disabled by stay_processing.enabled; exiting.\n";
    exit(0);
}

$lastRun = is_file($stateFile)
    ? (int)trim((string)file_get_contents($stateFile))
    : 0;

$nextDue = $lastRun + ($workerIntervalMin * 60);

if ($lastRun > 0 && time() < $nextDue) {
    printf(
        "YachtMemory Stay Processing - scheduled tick skipped; "
        . "next application run due in %d second(s).\n",
        max(1, $nextDue - time())
    );
    exit(0);
}

/*
 * Mark the application cycle before processing starts. This prevents a failing
 * cycle from being restarted on every technical timer tick.
 */
if (file_put_contents($stateFile, (string)time(), LOCK_EX) === false) {
    fwrite(
        STDERR,
        "STAY PROCESSING FAILED: cannot update scheduler state file.\n"
    );
    exit(1);
}

$runId = null;

try {
    $runId = StayProcessingRunWriter::begin(
        $vesselId,
        'SCHEDULED'
    );

    $result = StayProcessingService::runForVessel(
        $vesselId,
        $limit,
        $retryIntervalMin,
        false
    );

    StayProcessingRunWriter::complete(
        $runId,
        $result,
        isset($result['finished_at'])
            ? (string)$result['finished_at']
            : null
    );

    $result['run_id'] = $runId;
    $result['trigger_type'] = 'SCHEDULED';
    $result['worker_interval_min'] = $workerIntervalMin;

    echo json_encode(
        $result,
        JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
    ) . PHP_EOL;

    exit(0);

} catch (Throwable $e) {
    if ($runId !== null) {
        try {
            StayProcessingRunWriter::fail(
                $runId,
                get_class($e) . ': ' . $e->getMessage()
            );
        } catch (Throwable $loggingError) {
            fwrite(
                STDERR,
                'Run logging error: '
                . get_class($loggingError)
                . ': '
                . $loggingError->getMessage()
                . PHP_EOL
            );
        }
    }

    fwrite(
        STDERR,
        get_class($e) . ': ' . $e->getMessage() . PHP_EOL
    );
    exit(1);
}
