<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Unraid GraphQL API diagnostic. Reports whether this host has a usable API key, what the
//   PHP environment can do, and what the API actually answers — including live schema
//   introspection for the types the monitor layer depends on.
//
// OPERATIONAL MODEL
//   A hand-run tool, not part of any page. Nothing in the UI links here; it is opened
//   directly at /plugins/varaverk/api/api_test.php when the monitor page starts showing
//   degraded detail and the question is whether the API, the key, or the parsing is at fault.
//
//   It exists because that question was expensive to answer. The Unraid API renamed and
//   removed types between 7.2.5 and 7.3 — ArrayParity and ArrayCache stopped being distinct
//   types — and the only reliable way to know what the running version exposes is to ask it.
//
// DESIGN PRINCIPLES
//   Reports the environment before the result.
//       Key presence, curl availability and allow_url_fopen are answered first, because a
//       failed probe means something entirely different depending on those three.
//
//   Two transports, same query.
//       curl when available, a stream context otherwise. The fallback exists so the
//       diagnostic still returns something on a PHP build where the probe's failure would
//       otherwise be indistinguishable from the API being down.
//
//   Returns raw alongside decoded.
//       probe_raw carries the first 1000 bytes verbatim. When the response is not JSON — an
//       HTML error page, a proxy interception — the decoded field is null and the raw text is
//       the only thing that explains why.
//
//   Introspects the specific types the monitor layer reads.
//       Not a full schema dump. The named type lists are the ones whose field names the
//       collectors depend on, so a rename shows up here as a missing field rather than as a
//       quietly empty card on the monitor page.
//
//   Pretty-printed on purpose. The only consumer is a person reading it in a browser tab.
//
// OPERATIONAL SAFEGUARDS
//   Read-only. Every query is a read or an introspection; nothing here mutates array, docker,
//   or VM state through the API.
//
//   Every request is time-boxed.
//       5s with a 3s connect timeout on the probe, 8s on each introspection call. A hung or
//       unreachable API returns a diagnostic rather than becoming one.
//
//   The whole probe is skipped without a key, and reports that as the finding.
//       key_present is false and probe stays null. The absence of a key is the most common
//       answer this tool gives, and it is reported as a result rather than as a failure.
//
//   Localhost only. The endpoint is hardcoded to http://localhost/graphql — no part of the
//   request selects a target, so this cannot be used to probe another host.
//
//   The key is truncated in the response.
//       Only the first 8 characters are returned, as key_prefix — enough to confirm which key
//       is in use, not enough to use it.
//
//   Known exposure: this returns more than a normal endpoint should.
//       A key prefix, the cached API debug log, and the live schema are all visible to anyone
//       with a WebGUI session. That is acceptable for a diagnostic reachable only by typing
//       its URL, but it is the reason it is not linked from any page and should not be
//       wrapped in one. It is a pure GET read, so Unraid's POST-only CSRF guard does not
//       apply to it — the WebGUI session is its whole boundary. See README-unraid.md.
//
// REQUEST
//   GET, no parameters
//
// RESPONSE
//   {"host","key_present","key_prefix","curl_available","allow_url_fopen","debug_log",
//    "probe":{"url","http_code","curl_err","decoded"},"probe_raw",
//    "schema","pool_drives","schema2"}
//   The schema, pool_drives and schema2 keys are present only when a key exists.
//
// DEPENDS ON
//   include/config.php   vv_detect_host(), vv_conf_vars(), VV_CACHE_DIR
//   Unraid API           http://localhost/graphql
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';

$hostId = vv_detect_host();
$vars   = vv_conf_vars();
$key    = $vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '';

$result = [
    'host'        => $hostId,
    'key_present' => $key !== '',
    'key_prefix'  => $key ? substr($key, 0, 8) . '...' : null,
    'curl_available' => function_exists('curl_init'),
    'allow_url_fopen'=> (bool)ini_get('allow_url_fopen'),
    'debug_log'   => null,
    'probe'       => null,
    'probe_raw'   => null,
];

// Show last debug log if present
$debugFile = VV_CACHE_DIR . '/vv_api_debug.json';
if (file_exists($debugFile)) {
    $result['debug_log'] = json_decode(file_get_contents($debugFile), true);
}

// Run a minimal probe query
if ($key) {
    $url  = 'http://localhost/graphql';
    $body = json_encode(['query' => '{ info { os { hostname } } }']);

    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_HTTPHEADER     => ['Content-Type: application/json', "x-api-key: {$key}"],
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 5,
            CURLOPT_CONNECTTIMEOUT => 3,
        ]);
        $raw      = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr  = curl_error($ch);
        curl_close($ch);
    } else {
        $ctx = stream_context_create(['http' => [
            'method'        => 'POST',
            'header'        => "Content-Type: application/json\r\nx-api-key: {$key}",
            'content'       => $body,
            'timeout'       => 5,
            'ignore_errors' => true,
        ]]);
        $raw      = @file_get_contents($url, false, $ctx);
        $httpCode = $raw !== false ? 200 : 0;
        $curlErr  = '';
    }

    $result['probe'] = [
        'url'       => $url,
        'http_code' => $httpCode,
        'curl_err'  => $curlErr ?: null,
        'decoded'   => json_decode((string)$raw, true),
    ];
    $result['probe_raw'] = substr((string)$raw, 0, 1000);

// ── Schema introspection — discover actual field names ────────────────────────
if ($key) {
    // ArrayParity and ArrayCache were separate named types in 7.2.5 — removed in 7.3 (now same as ArrayDisk)
    $types = ['InfoOs','InfoCpu','InfoMemory','ArrayDisk','VmDomain'];
    $introspectGql = '{ ' . implode(' ', array_map(fn($t) =>
        "{$t}: __type(name: \"{$t}\") { fields { name type { name kind ofType { name kind } } } }",
        $types
    )) . ' }';

    $body2 = json_encode(['query' => $introspectGql]);
    $ch2 = curl_init('http://localhost/graphql');
    curl_setopt_array($ch2, [
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', "x-api-key: {$key}"],
        CURLOPT_POSTFIELDS     => $body2,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 8,
    ]);
    $raw2 = curl_exec($ch2);
    curl_close($ch2);
    $result['schema'] = json_decode((string)$raw2, true)['data'] ?? null;

    // Pool drive names — what does the API actually return for cache/pool drives?
    $ch_pools = curl_init('http://localhost/graphql');
    curl_setopt_array($ch_pools, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
        CURLOPT_POSTFIELDS => json_encode(['query' => '{ array { caches { name device type status fsType } } }']),
        CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8,
    ]);
    $result['pool_drives'] = json_decode((string)curl_exec($ch_pools), true)['data'] ?? null;
    curl_close($ch_pools);

    // Round 2: introspect CpuUtilization and MemoryUtilization field names
    $types2 = ['CpuUtilization','MemoryUtilization','TemperatureMetrics'];
    $gql2 = '{ ' . implode(' ', array_map(fn($t) =>
        "{$t}: __type(name: \"{$t}\") { kind fields { name type { name kind ofType { name kind } } } }",
        $types2
    )) . ' }';
    $ch3 = curl_init('http://localhost/graphql');
    curl_setopt_array($ch3, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
        CURLOPT_POSTFIELDS => json_encode(['query' => $gql2]),
        CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8,
    ]);
    $result['schema2'] = json_decode((string)curl_exec($ch3), true)['data'] ?? null;
    curl_close($ch3);
}
}

echo json_encode($result, JSON_PRETTY_PRINT);
