<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Background cache writer. Builds the full monitor payload and the arrs payload once a
//   minute and writes them to the cache, so page loads serve from a file instead of paying
//   for collection.
//
// OPERATIONAL MODEL
//   This is what makes the monitor and arrs tabs fast. api/monitor.php and api/arrs.php read
//   the files this process writes and only fall back to collecting for themselves on a miss.
//   The expensive work — GraphQL, docker stats, SSH to partners, HTTP to every arr instance —
//   happens here, on a schedule, off the request path.
//
//   Runs from cron every minute via api_cache_writer.sh, a bash wrapper the scheduler
//   requires. Not reachable over HTTP, and refuses to run if it ever is.
//
//   The payload assembled here is deliberately identical to api/monitor.php's. The two are
//   maintained together: a field added there and not here is a field that is only ever served
//   on a cache miss.
//
// DESIGN PRINCIPLES
//   One API round trip for the whole payload.
//       vv_api_data() is called once up front and static-cached for the life of the process,
//       so the API-first collectors below share a single GraphQL query rather than issuing
//       one each.
//
//   Writes two caches, not one.
//       monitor and arrs have different consumers and different costs, so they are written
//       under separate keys and either can be served while the other is stale.
//
//   Every payload carries its own timestamp, so consumers can render age rather than
//   presenting minute-old numbers as current.
//
//   Reports its own duration on stdout.
//       The elapsed time goes to the job log, which is the only place a slow collection cycle
//       becomes visible — this process has no other output and no failure anyone would see.
//
// OPERATIONAL SAFEGUARDS
//   Refuses to run under a web server.
//       PHP_SAPI is checked first and a non-CLI invocation is answered with a 404 and no
//       output. Otherwise a browser hitting this path would trigger the full expensive
//       collection — including SSH to every partner — outside any cache or rate limit, once
//       per request.
//
//   Read-only with respect to the system. Every collector observes; none start, stop, or
//   change anything. The only writes are the two cache files.
//
//   Cache writes are atomic.
//       vv_cache_write() writes a .tmp and renames, so a page load landing mid-write reads
//       the previous complete payload rather than a truncated one.
//
//   Every collector degrades to empty rather than fatal.
//       The library suppresses its filesystem reads and redirects stderr on every shell call,
//       so absent hardware yields an empty section. On a payload this wide that property is
//       what keeps one missing subsystem from failing the whole cycle and leaving both caches
//       to expire.
//
//   A failed cycle is survivable by design.
//       Nothing here clears the previous cache before building the new one. A run that dies
//       partway leaves the last good payload in place, and the readers' own age windows —
//       300s for monitor, 300s for arrs — are several cycles wide, so a single missed minute
//       is invisible.
//
// OUTPUT
//   VV_CACHE_DIR/monitor.json   consumed by api/monitor.php
//   VV_CACHE_DIR/arrs.json      consumed by api/arrs.php
//   stdout                      one timing line, captured into the job log
//
// DEPENDS ON
//   include/monitor.php, include/common.php, include/unraid_api.php,
//   include/vms.php, include/docker_folders.php, include/arrs.php
//   Tools/api_cache_writer.sh   the bash wrapper cron actually invokes
// ═══════════════════════════════════════════════════════════════════════════════════════════════

// Collecting this payload means GraphQL, docker stats and SSH to every partner. It must never
// be triggerable by an HTTP request.
if (PHP_SAPI !== 'cli') {
    http_response_code(404);
    exit(1);
}

$_base = dirname(__DIR__);
require_once $_base . '/include/monitor.php';
require_once $_base . '/include/vms.php';
require_once $_base . '/include/docker_folders.php';
require_once $_base . '/include/arrs.php';

$t = microtime(true);

// ── Monitor payload ───────────────────────────────────────────────────────────
// Call vv_api_data() once — result is static-cached for the rest of this process.
vv_api_data();

$monitor = [
    'system'          => vv_system_info(),
    'fallback'        => vv_fallback_state(),
    'fallback_active' => vv_fallback_active(),
    'partner'         => vv_partner_state(),
    'resources'       => vv_system_resources(),
    'cpu'             => vv_cpu_per_core(),
    'mem'             => vv_memory_breakdown(),
    'net'             => vv_network_stats(),
    'gpu'             => vv_gpu_stats(),
    'gpus'            => vv_gpu_stats_all(),
    'gpu_procs'       => vv_gpu_processes(),
    'containers'      => vv_docker_containers(),
    'stopped'         => vv_docker_stopped(),
    'transcode'       => vv_transcode_sessions(),
    'ups'             => vv_ups_stats(),
    'parity'          => vv_parity_status(),
    'storage'         => vv_storage_pools(),
    'array_disks'     => vv_array_disks(),
    'disk_io'         => vv_disk_io_rates(),
    'watchdog'        => vv_watchdog_summary(),
    'scripts'         => vv_scripts_status(),
    'rsync'           => vv_rsync_status(),
    'thresholds'      => vv_disk_thresholds(),
    'vms'             => vv_get_vms(),
    'docker_folders'  => vv_get_docker_folders(),
    'remote_hosts'    => vv_remote_hosts_stats(),
    '_api_status'     => vv_api_get_status(),
    'ts'              => time(),
];
vv_cache_write('monitor', $monitor);

// ── Arrs payload ──────────────────────────────────────────────────────────────
$arrs = vv_arrs_all();
vv_cache_write('arrs', $arrs);

$elapsed = round((microtime(true) - $t) * 1000);
echo "Cache written in {$elapsed}ms — monitor + arrs\n";
