<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Setup checklist. Evaluates what this host still needs before it is fully configured —
//   identity, conf, API and SSH keys, service discovery, media keys, and partnership state —
//   and names the action that fixes each gap.
//
// OPERATIONAL MODEL
//   Derived, never stored. Every item is computed from the conf files and the setup state on
//   each request, so the checklist cannot go stale or disagree with reality. There is no
//   "completed" flag anyone could tick.
//
//   The item list is not fixed. Emby and Jellyfin checks appear only once their container is
//   configured; the master.conf pull appears only on a partner host; partnership appears only
//   once a HOST2 is named. A checklist that listed everything would tell a single-host
//   install it was permanently incomplete.
//
//   Items carry an action name, not a URL. The page maps create_key, ssh_setup, run_populate,
//   pull_master and onboard onto the right endpoint or instruction — so this file describes
//   what is wrong, and the UI owns how to fix it.
//
// DESIGN PRINCIPLES
//   Each check answers the narrowest useful question.
//       The SSH item tests that the configured path exists on disk, not merely that a path is
//       set — a path set to a file that was never generated is the actual failure mode, and
//       the detail text distinguishes the two cases.
//
//   Auto-populate is satisfied by any one service.
//       The check passes on the first arr key or media container found. Requiring all of them
//       would leave the item permanently red on a host that legitimately runs only some.
//
//   Partnership reports its phases separately.
//       Phase 1 done with phase 2 outstanding is its own message, because the fix is to go
//       finish onboarding on the other host — not to re-run anything here.
//
//   State keys are read case-insensitively.
//       Both HOST2_PHASE1_DONE and host2_phase1_done are accepted, because the state file has
//       been written by both the shell layer and the PHP layer over its life.
//
//   complete is derived from the items, not tracked.
//       A single strict in_array(false, …) over the item results, so the summary can never
//       disagree with the list it summarises.
//
// OPERATIONAL SAFEGUARDS
//   Read-only. This endpoint diagnoses and never fixes — every remedy is a separate,
//   explicitly invoked action. That separation is what makes it safe to poll.
//
//   No input at all. There are no parameters, so there is nothing to validate and no way to
//   ask about a host other than this one.
//
//   An unidentified host degrades to a report rather than an error.
//       vv_detect_host() returning 'unknown' is handled at every use — the host conf is not
//       read, and the identity and host_conf items say so explicitly. That is the exact state
//       a fresh install is in, and it is the checklist's job to describe it.
//
//   Every conf read is defaulted.
//       vv_read_conf_raw() returns empty for a missing file and every scalar lookup is
//       trimmed with a ?? fallback, so a partial or absent conf yields items marked not-ok
//       rather than a fatal that would blank the whole panel.
//
//   Key presence is reported, key values never are.
//       The API, SSH, Emby and Jellyfin items report only whether a value is set — and for
//       SSH, the basename of the path. No credential is returned.
//
//   Missing is reported as missing, never as fine.
//       Every item defaults to ok:false and is only set true by a positive test. A check that
//       cannot run reports the gap it could not rule out.
//
// REQUEST
//   GET, no parameters
//
// RESPONSE
//   {"ok":true,"complete":bool,"host_id":"host<n>|unknown",
//    "items":[{"id","label","ok","detail","action"?}, …]}
//   action is present and non-null only when there is a remedy the UI can invoke.
//
// DEPENDS ON
//   include/config.php   vv_detect_host(), vv_read_conf_raw(), vv_parse_conf_scalar(),
//                        vv_setup_state_read(), CONF_DIR
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';

$hostId   = vv_detect_host();
$hostIdUp = strtoupper($hostId);
$master   = vv_read_conf_raw('master.conf');
$confRaw  = ($hostId !== 'unknown') ? vv_read_conf_raw($hostId . '.conf') : '';

$items = [];

// ── Identity ──────────────────────────────────────────────────────────────────
preg_match('/^\s*HOST1\s*=\s*"([^"]*)"/m', $master, $m1);
$host1 = trim($m1[1] ?? '');
$items[] = [
    'id'     => 'identity',
    'label'  => 'Server identity',
    'ok'     => !empty($host1),
    'detail' => $host1 ? "HOST1: $host1" : 'HOST1 blank in master.conf',
];

// ── Host conf ─────────────────────────────────────────────────────────────────
$confExists = $hostId !== 'unknown' && file_exists(CONF_DIR . '/' . $hostId . '.conf');
$items[] = [
    'id'     => 'host_conf',
    'label'  => 'Host configuration',
    'ok'     => $confExists,
    'detail' => $confExists
        ? "$hostId.conf present"
        : ($hostId === 'unknown' ? 'Server not yet identified' : "$hostId.conf missing"),
];

// ── Unraid API key ─────────────────────────────────────────────────────────────
$apiKey = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_UNRAID_API_KEY'));
$items[] = [
    'id'     => 'api_key',
    'label'  => 'Unraid API key',
    'ok'     => !empty($apiKey),
    'detail' => $apiKey ? 'Key present' : 'Not set',
    'action' => $apiKey ? null : 'create_key',
];

// ── SSH key ────────────────────────────────────────────────────────────────────
$sshPath = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_SSH_KEY'));
$sshOk   = $sshPath && file_exists($sshPath);
$items[] = [
    'id'     => 'ssh_key',
    'label'  => 'SSH key',
    'ok'     => $sshOk,
    'detail' => $sshOk
        ? basename($sshPath)
        : ($sshPath ? "Path set but file missing: $sshPath" : 'No key path in host.conf'),
    'action' => $sshOk ? null : 'ssh_setup',
];

// ── Auto-populate (any service key or container detected) ──────────────────────
$populated = false;
foreach (['_RADARR_API_KEY','_SONARR_API_KEY','_LIDARR_API_KEY','_EMBY_CONTAINER','_JELLYFIN_CONTAINER'] as $f) {
    if (trim(vv_parse_conf_scalar($confRaw, $hostIdUp . $f)) !== '') {
        $populated = true;
        break;
    }
}
$items[] = [
    'id'     => 'populated',
    'label'  => 'Auto-populate',
    'ok'     => $populated,
    'detail' => $populated ? 'Services detected in host.conf' : 'No services detected yet',
    'action' => $populated ? null : 'run_populate',
];

// ── Emby API key ───────────────────────────────────────────────────────────────
$embyContainer = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_EMBY_CONTAINER'));
if (!empty($embyContainer)) {
    $embyKey = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_EMBY_API_KEY'));
    $items[] = [
        'id'     => 'emby_key',
        'label'  => 'Emby API key',
        'ok'     => !empty($embyKey),
        'detail' => $embyKey
            ? 'Key present'
            : 'Not set — Emby Dashboard → API Keys → + New Key',
    ];
}

// ── Jellyfin API key ───────────────────────────────────────────────────────────
$jfContainer = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_JELLYFIN_CONTAINER'));
if (!empty($jfContainer)) {
    $jfKey = trim(vv_parse_conf_scalar($confRaw, $hostIdUp . '_JELLYFIN_API_KEY'));
    $items[] = [
        'id'     => 'jellyfin_key',
        'label'  => 'Jellyfin API key',
        'ok'     => !empty($jfKey),
        'detail' => $jfKey
            ? 'Key present'
            : 'Not set — Jellyfin Dashboard → Administration → API Keys',
    ];
}

// ── master.conf pull (partner servers only) ───────────────────────────────────────────────────
if ($hostId !== 'host1' && $hostId !== 'unknown') {
    $state   = vv_setup_state_read();
    $pulled  = !empty($state['master_conf_pulled']);
    $items[] = [
        'id'     => 'master_conf',
        'label'  => 'master.conf',
        'ok'     => $pulled,
        'detail' => $pulled
            ? 'Synced from HOST1'
            : ($host1 ? "Not yet pulled from $host1" : 'HOST1 hostname not set in master.conf'),
        'action' => (!$pulled && $host1) ? 'pull_master' : null,
    ];
}

// ── Partnership (only if a partner is configured) ──────────────────────────────
preg_match('/^\s*HOST2\s*=\s*"([^"]*)"/m', $master, $m2);
$host2 = trim($m2[1] ?? '');
if (!empty($host2)) {
    $state  = vv_setup_state_read();
    $p1done = !empty($state['HOST2_PHASE1_DONE']) || !empty($state['host2_phase1_done']);
    $p2done = !empty($state['HOST2_PHASE2_DONE']) || !empty($state['host2_phase2_done']);
    $items[] = [
        'id'     => 'partnership',
        'label'  => 'Partnership',
        'ok'     => $p1done && $p2done,
        'detail' => ($p1done && $p2done)
            ? "Active with $host2"
            : ($p1done ? "Phase 1 done — waiting for HOST2 to complete" : "Not started — run partnership_onboard.sh"),
        'action' => (!$p1done) ? 'onboard' : null,
    ];
}

$allOk = !in_array(false, array_column($items, 'ok'), true);

echo json_encode(['ok' => true, 'complete' => $allOk, 'host_id' => $hostId, 'items' => $items]);
