<?php
// web-files/pages/account-valkey.php
// Per-customer Valkey (Redis-compatible cache) management.
// auto-prepend.php handles auth and $_SESSION['csrf_token'].
//
// When root is logged in, they are managing a customer's Valkey; they cannot
// create one for the "root" account via this page. For server-side caching,
// manage a Valkey container directly through docker-management.
$csrf_token = $_SESSION['csrf_token'] ?? '';
$is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
?>
<div class="container py-4">
  <div class="alert alert-info">
    <p class="mb-2">
      <strong>What is Valkey?</strong> Valkey is the open-source successor to Redis — a fast
      in-memory key/value store. It is fully wire-compatible with Redis, so any Redis client
      library (phpredis, Predis, ioredis, etc.) works unchanged. Connect from your code using
      the hostname <code>valkey</code> on port <code>6379</code>; no password required
      (the connection is network-isolated to your account).
    </p>
    <details>
      <summary class="fw-bold" style="cursor:pointer">Common uses and sizing guidelines</summary>
      <div class="mt-2">
        <div class="row g-3">
          <div class="col-md-6">
            <h6 class="mb-1">Common uses</h6>
            <ul class="small mb-0">
              <li><strong>Application caching</strong> — database query results, rendered HTML, API responses</li>
              <li><strong>Sessions</strong> — PHP sessions shared across requests and site containers</li>
              <li><strong>Rate limiting</strong> — request counters, sliding-window throttles</li>
              <li><strong>Background queues</strong> — lightweight task queues (e.g., for email sending, image processing)</li>
              <li><strong>Real-time features</strong> — pub/sub notifications, live counters, leaderboards</li>
            </ul>
          </div>
          <div class="col-md-6">
            <h6 class="mb-1">Sizing guidelines</h6>
            <ul class="small mb-2">
              <li><strong>32 MB</strong> — small-site sessions, rate limiting, light caching</li>
              <li><strong>64 MB</strong> — typical WordPress object cache, medium session volume</li>
              <li><strong>128 MB</strong> — busy site with extensive API/page caching</li>
              <li><strong>192–256 MB</strong> — high-traffic app, heavy caching, larger working sets</li>
            </ul>
            <p class="small text-muted mb-0">
              Start at the low end and monitor <em>Memory used</em> on the manage panel.
              If eviction rates climb or you hit the cap, resize up — it's a one-click restart.
            </p>
          </div>
        </div>
        <div class="small text-muted mt-2 mb-0">
          <strong>Mode tip:</strong> choose <em>Cache-only</em> if data is OK to lose on restart
          (pure caching). Choose <em>Snapshot</em> if you use Valkey for sessions, queues, or
          anything the app doesn't want to lose — data persists to disk every 5 minutes.
        </div>
      </div>
    </details>
  </div>

  <?php if ($is_root): ?>
  <div class="card mb-4">
    <div class="card-header">Manage Valkey for customer</div>
    <div class="card-body">
      <label for="vk-target-user" class="form-label">Customer account</label>
      <select class="form-select" id="vk-target-user">
        <option value="">— Select a customer —</option>
      </select>
      <div class="form-text text-muted">
        Root cannot create a Valkey instance for its own account.
        For server-side caching, manage a Valkey container via
        <a href="index.php?page=docker-management">Docker Management</a>.
      </div>
    </div>
  </div>
  <?php endif; ?>

  <div id="valkey-status" class="card mb-4"<?php echo $is_root ? ' style="display:none"' : ''; ?>>
    <div class="card-body"><em>Loading status…</em></div>
  </div>

  <div id="valkey-enable-form" class="card" style="display:none">
    <div class="card-header">Enable Valkey</div>
    <div class="card-body">
      <div class="mb-3">
        <label class="form-label">Memory cap: <span id="vk-memory-label">32 MB</span></label>
        <input type="range" class="form-range" id="vk-memory" min="32" max="256" step="32" value="32">
        <div class="d-flex justify-content-between small text-muted">
          <span>32 MB</span><span>256 MB</span>
        </div>
        <div class="form-text text-muted">Counts against your account's RAM allowance.</div>
      </div>
      <div class="mb-3">
        <label class="form-label">Mode</label>
        <select class="form-select" id="vk-mode">
          <option value="cache" selected>Cache-only (data lost on restart)</option>
          <option value="snapshot">Snapshot (data persists across restarts)</option>
        </select>
      </div>
      <div class="mb-3">
        <label class="form-label">Eviction policy (when cap is reached)</label>
        <select class="form-select" id="vk-policy">
          <option value="allkeys-lru" selected>allkeys-lru — evict least-recently-used</option>
          <option value="allkeys-lfu">allkeys-lfu — evict least-frequently-used</option>
          <option value="volatile-lru">volatile-lru — evict LRU keys with TTL</option>
          <option value="volatile-ttl">volatile-ttl — evict soonest-expiring</option>
          <option value="noeviction">noeviction — writes fail when full</option>
        </select>
      </div>
      <div class="alert alert-light small mb-3">
        <strong>Restoring from a previous snapshot?</strong>
        Use the <a href="index.php?page=backups">Backup Manager</a> to restore
        <code>valkey/data/dump.rdb</code> from an earlier backup into your home directory
        <em>before</em> clicking Enable — Valkey auto-loads the file on startup if it's there.
      </div>
      <button class="btn btn-primary" id="vk-enable-btn">Enable Valkey</button>
    </div>
  </div>

  <div id="valkey-manage" class="card" style="display:none">
    <div class="card-header">Manage Valkey</div>
    <div class="card-body">
      <dl class="row" id="vk-info-grid"></dl>
      <hr>
      <div class="mb-3">
        <label class="form-label">Resize memory cap: <span id="vk-resize-label">32 MB</span></label>
        <input type="range" class="form-range" id="vk-resize-mem" min="32" max="256" step="32" value="32">
        <div class="d-flex justify-content-between small text-muted">
          <span>32 MB</span><span>256 MB</span>
        </div>
        <button class="btn btn-secondary mt-2" id="vk-resize-btn">Resize (restarts container)</button>
      </div>
      <button class="btn btn-warning" id="vk-recreate-btn">Recreate container</button>
      <span class="text-muted ms-2 d-block mt-1">
        Useful after a security patch. Pulls from the pinned image and replaces the running container.
        Snapshot-mode data persists via dump.rdb on disk; cache-mode data is lost.
      </span>
      <hr>
      <button class="btn btn-danger" id="vk-disable-btn">Disable Valkey</button>
      <span class="text-muted ms-2">Immediate teardown. Final snapshot saved to your next backup (Snapshot mode only).</span>
    </div>
  </div>
</div>

<script>
(function() {
    var csrfToken = <?php echo json_encode($csrf_token); ?>;
    var isRoot    = <?php echo json_encode($is_root); ?>;
    var $statusCard = document.getElementById('valkey-status');
    var $statusBody = document.querySelector('#valkey-status .card-body');
    var $enableForm = document.getElementById('valkey-enable-form');
    var $manage     = document.getElementById('valkey-manage');
    var $infoGrid   = document.getElementById('vk-info-grid');
    var $resizeMem  = document.getElementById('vk-resize-mem');
    var $targetUser = document.getElementById('vk-target-user'); // null for non-root

    var targetUser = ''; // root: filled when customer is selected. non-root: ignored by server.
    var refreshTimer = null;

    function clearChildren(el) {
        while (el.firstChild) el.removeChild(el.firstChild);
    }

    function withTarget(url) {
        if (isRoot && targetUser) {
            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'target_user=' + encodeURIComponent(targetUser);
        }
        return url;
    }

    function apiGet(action) {
        return fetch(withTarget('/api/valkey.php?action=' + encodeURIComponent(action)), {
            headers: {'X-CSRF-Token': csrfToken, 'Accept': 'application/json'},
            credentials: 'same-origin'
        }).then(function(r) {
            return r.text().then(function(body) {
                return {ok: r.ok, status: r.status, body: body};
            });
        });
    }

    function apiPost(action, data) {
        data = data || {};
        data.action = action;
        if (isRoot && targetUser) data.target_user = targetUser;
        return fetch('/api/valkey.php', {
            method: 'POST',
            headers: {'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken, 'Accept': 'application/json'},
            credentials: 'same-origin',
            body: JSON.stringify(data)
        }).then(function(r) {
            return r.text().then(function(body) {
                return {ok: r.ok, status: r.status, body: body};
            });
        });
    }

    function parseJson(resp) {
        try { return JSON.parse(resp.body); } catch (e) { return null; }
    }

    function renderStatusError(resp, extra) {
        clearChildren($statusBody);
        var wrap = document.createElement('div');
        wrap.className = 'text-danger';
        var strong = document.createElement('strong');
        strong.textContent = 'Status request failed: ';
        wrap.appendChild(strong);
        wrap.appendChild(document.createTextNode(
            'HTTP ' + (resp ? resp.status : '?') + (extra ? ' (' + extra + ')' : '')
        ));
        if (resp && resp.body) {
            wrap.appendChild(document.createElement('br'));
            var small = document.createElement('small');
            small.className = 'text-muted';
            small.textContent = resp.body.substring(0, 400);
            wrap.appendChild(small);
        }
        $statusBody.appendChild(wrap);
    }

    function renderStatusText(label, value) {
        clearChildren($statusBody);
        var strong = document.createElement('strong');
        strong.textContent = label + ' ';
        $statusBody.appendChild(strong);
        $statusBody.appendChild(document.createTextNode(value));
    }

    function row(k, v) {
        var dt = document.createElement('dt'); dt.className = 'col-sm-4'; dt.textContent = k;
        var dd = document.createElement('dd'); dd.className = 'col-sm-8'; dd.textContent = v;
        $infoGrid.appendChild(dt); $infoGrid.appendChild(dd);
    }

    function refresh() {
        apiGet('status').then(function(resp) {
            if (!resp.ok) { renderStatusError(resp); return; }
            var data = parseJson(resp);
            if (!data || !data.success) {
                renderStatusError(resp, data && data.error ? data.error : 'empty response');
                return;
            }
            var info = data.info;
            if (info.status === 'not_enabled') {
                renderStatusText('Status:', 'Not enabled');
                $enableForm.style.display = '';
                $manage.style.display = 'none';
                // Valkey auto-loads /home/$user/valkey/data/dump.rdb on boot if present,
                // so no client-side backup selection is needed. Customers wanting to
                // restore an older snapshot do so via the Backup Manager before Enable.
            } else {
                $enableForm.style.display = 'none';
                $manage.style.display = '';
                renderStatusText('Status:', info.status);
                clearChildren($infoGrid);
                row('Mode', info.row.mode);
                row('Memory cap', info.row.memory_mb + ' MB');
                row('Eviction', info.row.eviction_policy);
                if (info.status === 'running') {
                    row('Memory used', Math.round((info.memory_used_bytes || 0) / 1048576) + ' MB');
                    row('Keys', info.key_count);
                    row('Uptime', Math.round(info.uptime_sec / 60) + ' min');
                    if (info.row.mode === 'snapshot' && info.last_save_unix) {
                        row('Last snapshot', new Date(info.last_save_unix * 1000).toLocaleString());
                    }
                }
                $resizeMem.value = info.row.memory_mb;
                document.getElementById('vk-resize-label').textContent = info.row.memory_mb + ' MB';
            }
        }).catch(function(err) {
            renderStatusError(null, err && err.message ? err.message : String(err));
        });
    }

    // Live label sync for both sliders.
    (function() {
        var memSlider = document.getElementById('vk-memory');
        var memLabel  = document.getElementById('vk-memory-label');
        memSlider.addEventListener('input', function() { memLabel.textContent = memSlider.value + ' MB'; });
        $resizeMem.addEventListener('input', function() {
            document.getElementById('vk-resize-label').textContent = $resizeMem.value + ' MB';
        });
    })();

    document.getElementById('vk-enable-btn').addEventListener('click', function() {
        apiPost('enable', {
            memory_mb: parseInt(document.getElementById('vk-memory').value, 10),
            mode: document.getElementById('vk-mode').value,
            eviction_policy: document.getElementById('vk-policy').value
        }).then(function(resp) {
            var d = parseJson(resp);
            if (!resp.ok || !d || !d.success) {
                alert('Enable failed: ' + (d && d.error ? d.error : 'HTTP ' + resp.status));
            }
            refresh();
        });
    });

    document.getElementById('vk-resize-btn').addEventListener('click', function() {
        if (!confirm('Resize will restart Valkey. Cache-only data will be lost. Continue?')) return;
        apiPost('resize', {
            memory_mb: parseInt($resizeMem.value, 10)
        }).then(function(resp) {
            var d = parseJson(resp);
            if (!resp.ok || !d || !d.success) {
                alert('Resize failed: ' + (d && d.error ? d.error : 'HTTP ' + resp.status));
            }
            refresh();
        });
    });

    document.getElementById('vk-recreate-btn').addEventListener('click', function() {
        if (!confirm('Recreate the Valkey container now?\n\nThe running container is replaced with a fresh one using the pinned image. Snapshot-mode data persists via dump.rdb; cache-mode data is lost.')) return;
        apiPost('recreate', {}).then(function(resp) {
            var d = parseJson(resp);
            if (!resp.ok || !d || !d.success) {
                alert('Recreate failed: ' + (d && d.error ? d.error : 'HTTP ' + resp.status));
            }
            refresh();
        });
    });

    document.getElementById('vk-disable-btn').addEventListener('click', function() {
        if (!confirm('Disable Valkey? This immediately tears down the container and deletes in-memory data. A final snapshot will be saved to your next backup if you are in Snapshot mode.')) return;
        apiPost('disable', {}).then(function(resp) {
            var d = parseJson(resp);
            if (!resp.ok || !d || !d.success) {
                alert('Disable failed: ' + (d && d.error ? d.error : 'HTTP ' + resp.status));
            }
            refresh();
        });
    });

    function startPolling() {
        if (refreshTimer) return;
        refresh();
        refreshTimer = setInterval(refresh, 15000);
    }

    function stopPolling() {
        if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
    }

    if (isRoot) {
        // Populate the customer dropdown, then gate the rest of the UI on selection.
        apiGet('list_customers').then(function(resp) {
            var d = parseJson(resp);
            if (!resp.ok || !d || !d.success) {
                alert('Failed to load customer list: ' + (d && d.error ? d.error : 'HTTP ' + resp.status));
                return;
            }
            (d.customers || []).forEach(function(u) {
                var opt = document.createElement('option');
                opt.value = u; opt.textContent = u;
                $targetUser.appendChild(opt);
            });
        });

        $targetUser.addEventListener('change', function() {
            targetUser = $targetUser.value;
            stopPolling();
            if (!targetUser) {
                // Hide all management UI until a customer is selected.
                $statusCard.style.display = 'none';
                $enableForm.style.display = 'none';
                $manage.style.display = 'none';
                return;
            }
            $statusCard.style.display = '';
            // Reset status card to loading state; refresh() will fill it in.
            clearChildren($statusBody);
            var em = document.createElement('em'); em.textContent = 'Loading status…';
            $statusBody.appendChild(em);
            startPolling();
        });
    } else {
        // Non-root: manage own account directly.
        startPolling();
    }
})();
</script>
