<?php
/**
 * Deploy / cache diagnostic — SysAdmin only.
 *
 * Answers one question after a deploy: "is the live site actually running the
 * code I just uploaded?" Being a (relatively) new file, this page itself always
 * runs fresh — OPcache can't be holding a stale compiled copy of a path that
 * was just added — so it can read the REAL on-disk source of other files and
 * compare it to what OPcache has cached.
 *
 * Visit /admin/deploy-check.php after a deploy. Add ?reset=1 to attempt an
 * OPcache flush (works per PHP worker; on LiteSpeed LSAPI you may still need a
 * PHP restart from cPanel if multiple workers cache independently).
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$uid  = (int) $user['id'];
if (!user_has_role($uid, 'sysadmin')) {
    http_response_code(403);
    flash('error', 'SysAdmin only.');
    redirect('/dashboard.php');
}

// Files this deploy changed — each gets a marker string that ONLY exists in the NEW code (update per deploy).
$checks = [
    // ---- AR/Collections rebuild Phases 3-5 (2026-06-25) ----
    'admin/ar-work.php'        => 'PHI fence',                                  // P3 per-visit GetNext work + ownership fence
    'admin/ar-audit.php'       => 'open rebuttal on this visit',                // P4 auditor 5-param audit + rebuttal lock
    'admin/ar-rebuttals.php'   => 'You can only dispute parameters the auditor marked', // P4 worker rebuttal + empty-guard
    'admin/ar-sampling.php'    => 'AR audit sampling',                          // P4 per-user sample %
    'admin/billing-sampling.php' => 'Billing audit sampling',                   // per-biller billing sample %
    'admin/billing-rebuttals.php' => 'My billing audit errors',                 // biller error visibility + rebut
    'includes/billing.php'     => 'billing_user_sample_v1',                     // per-biller sample + rebuttal helper
    'admin/ar-reports.php'     => 'Inventory pivots',                           // P5 role-scoped reports + 4 pivots
    'includes/ar.php'          => 'ar_reports_perm_v1',                         // P4/P5 migrations: audit, worked_by, ar.reports
    'includes/auth.php'        => "'ar-reports.php'        => ['ar.reports']",   // P5 route gate
];

$didReset = false; $resetMsg = [];
if (($_GET['reset'] ?? '') === '1') {
    if (function_exists('opcache_reset'))      { $resetMsg[] = 'opcache_reset(): ' . (@opcache_reset() ? 'ok' : 'failed/no-op'); }
    if (function_exists('opcache_invalidate')) {
        foreach ($checks as $rel => $_m) { $f = realpath(__DIR__ . '/../' . $rel); if ($f) @opcache_invalidate($f, true); }
        $resetMsg[] = 'opcache_invalidate(): forced on ' . count($checks) . ' file(s)';
    }
    $didReset = true;
}

$status   = function_exists('opcache_get_status') ? @opcache_get_status(true) : null;
$ocOn     = is_array($status) && !empty($status['opcache_enabled']);
$validate = ini_get('opcache.validate_timestamps');
$freq     = ini_get('opcache.revalidate_freq');

header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: no-store');
?><!doctype html><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Deploy check — Medmecs</title>
<style>
  body{font:14px/1.5 system-ui,Segoe UI,sans-serif;max-width:920px;margin:24px auto;padding:0 16px;color:#1a1a1a}
  h1{font-size:20px} h2{font-size:15px;margin-top:24px;border-bottom:1px solid #eee;padding-bottom:4px}
  table{border-collapse:collapse;width:100%;margin:8px 0} th,td{border:1px solid #e3e3e3;padding:6px 9px;text-align:left;vertical-align:top}
  th{background:#f6f8fa} code{background:#f1f3f5;padding:1px 5px;border-radius:4px}
  .ok{color:#1a7f37;font-weight:600} .bad{color:#c00;font-weight:600} .warn{color:#9a6700;font-weight:600}
  .pill{display:inline-block;padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600}
  .pill.ok{background:#dafbe1} .pill.bad{background:#ffe3e3} .pill.warn{background:#fff3cd}
  .btn{display:inline-block;background:#0d6efd;color:#fff;padding:8px 14px;border-radius:6px;text-decoration:none;margin-top:8px}
  .muted{color:#666}
</style>
<h1>Deploy / cache check</h1>
<p class="muted">Server time: <?= date('Y-m-d H:i:s') ?> · PHP <?= PHP_VERSION ?> (<?= php_sapi_name() ?>) · <?= e($_SERVER['SERVER_SOFTWARE'] ?? '?') ?></p>

<?php if ($didReset): ?>
  <p class="pill warn">Reset attempted</p>
  <ul><?php foreach ($resetMsg as $m) echo '<li>' . e($m) . '</li>'; ?></ul>
  <p class="muted">Now reload this page (without <code>?reset=1</code>) and re-check the rows below.</p>
<?php endif; ?>

<h2>Is the new code on disk &amp; live?</h2>
<table>
  <tr><th>File</th><th>On-disk modified</th><th>New code on disk?</th><th>OPcache cached at</th><th>Verdict</th></tr>
  <?php foreach ($checks as $rel => $marker):
      $abs   = realpath(__DIR__ . '/../' . $rel);
      $src   = $abs ? @file_get_contents($abs) : false;
      $hasNew= $src !== false && strpos($src, $marker) !== false;
      $mtime = $abs ? @filemtime($abs) : 0;
      $cachedTs = null;
      if ($ocOn && $abs && !empty($status['scripts'][$abs])) {
          $cachedTs = $status['scripts'][$abs]['timestamp'] ?? null;   // when OPcache last compiled it
      }
      // Verdict logic
      if ($src === false)            { $v = '<span class="bad">file missing on server</span>'; }
      elseif (!$hasNew)              { $v = '<span class="bad">OLD code on disk — extract did not overwrite</span>'; }
      elseif ($cachedTs && $mtime && $cachedTs < $mtime)
                                     { $v = '<span class="warn">new on disk, but OPcache holds an older compile — reset needed</span>'; }
      else                          { $v = '<span class="ok">running new code</span>'; }
  ?>
  <tr>
    <td><code><?= e($rel) ?></code><br><span class="muted">marker: <code><?= e($marker) ?></code></span></td>
    <td><?= $mtime ? date('Y-m-d H:i:s', $mtime) : '—' ?></td>
    <td><?= $hasNew ? '<span class="ok">yes</span>' : '<span class="bad">no</span>' ?></td>
    <td><?= $cachedTs ? date('Y-m-d H:i:s', $cachedTs) : '<span class="muted">not cached</span>' ?></td>
    <td><?= $v ?></td>
  </tr>
  <?php endforeach; ?>
</table>

<h2>OPcache</h2>
<table>
  <tr><th>Enabled</th><td><?= $ocOn ? '<span class="ok">yes</span>' : '<span class="muted">no / unavailable</span>' ?></td></tr>
  <tr><th><code>opcache.validate_timestamps</code></th><td><?= $validate === false ? '—' : e((string)$validate) ?>
      <?= ($validate === '0' || $validate === 0) ? '<span class="bad">(0 = never re-checks files → stale code after deploy until a PHP restart)</span>' : '<span class="muted">(1 = re-checks file timestamps)</span>' ?></td></tr>
  <tr><th><code>opcache.revalidate_freq</code></th><td><?= $freq === false ? '—' : e((string)$freq) ?> s</td></tr>
  <?php if ($ocOn && isset($status['opcache_statistics']['last_restart_time'])):
      $lr = (int)$status['opcache_statistics']['last_restart_time']; ?>
  <tr><th>Last OPcache restart</th><td><?= $lr ? date('Y-m-d H:i:s', $lr) : 'never since boot' ?></td></tr>
  <?php endif; ?>
</table>

<p><a class="btn" href="?reset=1">Attempt OPcache flush now</a></p>
<p class="muted">If a file shows <b>OLD code on disk</b> → the upload/extract didn't overwrite it (re-extract with overwrite, or upload that file directly).<br>
If it shows <b>OPcache holds an older compile</b> → click the flush button; if it persists, switch PHP version off/on in cPanel (MultiPHP Manager) or ask GlobeHost to restart PHP.<br>
Delete this file once the deploy is confirmed — it's a debug tool.</p>
