// ========== AUDIT LOG VIEWER ==========
// DC-050: surface authenticated user identity (userEmail / userRole from
// auditLogger.details), add outcome filter, pass confirm=CLEAR body for
// destructive DELETE.
(function() {
  // Inject modal HTML
  injectModal('audit-modal', `<div id="audit-modal" class="weather-modal">
    <div class="weather-modal-content" style="min-width: 950px; max-width: 1150px;">
      <h3>📜 Audit Log</h3>
      <p class="modal-subtitle">
        Track all actions performed through the API.
      </p>

      <div style="display: flex; gap: 12px; margin-bottom: 12px; align-items: center; flex-wrap: wrap;">
        <label class="text-muted-sm">Category:</label>
        <select id="audit-filter" style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.85rem;">
          <option value="">All</option>
          <option value="service">Services</option>
          <option value="container">Containers</option>
          <option value="caddy">Caddy</option>
          <option value="dns">DNS</option>
          <option value="backup">Backups</option>
          <option value="config">Config</option>
          <option value="auth">Auth</option>
        </select>
        <label class="text-muted-sm">Result:</label>
        <select id="audit-outcome-filter" style="padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.85rem;">
          <option value="">Any</option>
          <option value="success">✓ Success</option>
          <option value="failure">✗ Failure</option>
        </select>
        <label class="text-muted-sm" style="margin-left: 8px;">Since:</label>
        <input id="audit-since" type="datetime-local" style="padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
        <label class="text-muted-sm">Until:</label>
        <input id="audit-until" type="datetime-local" style="padding: 5px 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); font-size: 0.82rem;">
        <button id="audit-refresh-btn" class="btn-sm">🔄 Refresh</button>
        <span style="flex: 1;"></span>
        <button id="audit-clear-btn" style="padding: 6px 12px; font-size: 0.8rem; color: var(--bad-fg); border-color: var(--bad-fg);">🗑️ Clear Log</button>
      </div>

      <div id="audit-log-container" class="scroll-container">
        <div class="panel-empty"><span class="brand-spinner"></span> Loading audit log...</div>
      </div>

      <div style="margin-top: 12px; text-align: center;">
        <button id="audit-load-more" style="display: none; padding: 6px 16px; font-size: 0.8rem;">Load More</button>
      </div>

      <div class="weather-modal-buttons modal-footer-bar">
        <button id="audit-cancel">Close</button>
      </div>
    </div>
  </div>`);

  const modal = document.getElementById('audit-modal');
  const openBtn = document.getElementById('audit-log-btn');
  const cancelBtn = document.getElementById('audit-cancel');
  const refreshBtn = document.getElementById('audit-refresh-btn');
  const clearBtn = document.getElementById('audit-clear-btn');
  const filterSelect = document.getElementById('audit-filter');
  const outcomeSelect = document.getElementById('audit-outcome-filter');
  const sinceInput = document.getElementById('audit-since');
  const untilInput = document.getElementById('audit-until');
  const container = document.getElementById('audit-log-container');
  const loadMoreBtn = document.getElementById('audit-load-more');
  let currentOffset = 0;
  let inflight = null; // AbortController for the in-flight request
  let filterNonce = 0; // increments on every fresh (non-append) load; lets
                       // an in-flight append detect the filter has changed
                       // and skip its DOM splice.
  const PAGE_SIZE = 50;

  // datetime-local fields carry no timezone offset — convert to ISO 8601
  // with the local offset so the server can compare correctly.
  function toIso(localDtValue) {
    if (!localDtValue) return null;
    // Browsers expose datetime-local as naive local time. new Date() on
    // that string parses it as LOCAL, so toISOString() yields the UTC
    // equivalent the server expects.
    const d = new Date(localDtValue);
    if (isNaN(d.getTime())) return null;
    return d.toISOString();
  }

  async function loadAudit(append) {
    try {
      if (!append) {
        // Cancel any pending request and bump the filter nonce so any
        // appending fetch (still in flight) knows to discard its response.
        if (inflight) inflight.abort();
        inflight = new AbortController();
        currentOffset = 0;
        filterNonce++;
        container.innerHTML = '<div class="panel-empty"><span class="brand-spinner"></span> Loading...</div>';
      } else {
        if (inflight) inflight.abort();
        inflight = new AbortController();
      }
      const myNonce = filterNonce;
      const params = new URLSearchParams();
      params.set('limit', String(PAGE_SIZE));
      params.set('offset', String(currentOffset));
      const action = filterSelect.value;
      const outcome = outcomeSelect.value;
      const since = toIso(sinceInput.value);
      const until = toIso(untilInput.value);
      if (action) params.set('action', action);
      if (outcome) params.set('outcome', outcome);
      if (since) params.set('since', since);
      if (until) params.set('until', until);

      const res = await fetch('/api/v1/audit-logs?' + params.toString(), {
        signal: inflight.signal,
      });
      // Surface 401/403/500 explicitly — the dashboard used to render any
      // non-success response as "no audit log entries yet," which is
      // misleading for an expired session.
      if (!res.ok) {
        container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: HTTP ${res.status}</div>`;
        loadMoreBtn.style.display = 'none';
        return;
      }
      const data = await res.json();
      if (!data.success) {
        container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(data.error || 'unknown')}</div>`;
        loadMoreBtn.style.display = 'none';
        return;
      }
      // If a non-append load happened after this fetch was issued, the
      // operator changed filters; discard the now-stale response.
      if (!append && myNonce !== filterNonce) return;
      const entries = Array.isArray(data.entries) ? data.entries : [];

      if (entries.length === 0 && !append) {
        const reason = data.filters && (data.filters.action || data.filters.outcome || data.filters.since || data.filters.until)
          ? 'No entries match your filters.'
          : 'No audit log entries yet. Actions will be logged automatically.';
        container.innerHTML = `<div class="panel-empty"><span class="empty-icon">📜</span>${escapeHtml(reason)}</div>`;
        loadMoreBtn.style.display = 'none';
        return;
      }

      let html = '';
      if (!append) {
        html = '<table style="width: 100%; border-collapse: collapse; font-size: 0.82rem;">';
        html += '<tr style="border-bottom: 1px solid var(--border); color: var(--muted);">';
        html += '<th style="padding: 6px; text-align: left;">When</th>';
        html += '<th style="padding: 6px; text-align: left;">Actor</th>';
        html += '<th style="padding: 6px; text-align: left;">IP</th>';
        html += '<th style="padding: 6px; text-align: left;">Action</th>';
        html += '<th style="padding: 6px; text-align: left;">Resource</th>';
        html += '<th style="padding: 6px; text-align: left;">Result</th>';
        html += '</tr>';
      }

      for (const e of entries) {
        const ok = e.outcome === 'success';
        const actor = actorLabel(e);
        html += `<tr style="border-bottom: 1px solid var(--border); cursor: pointer;" class="audit-row">`;
        html += `<td style="padding: 6px; color: var(--muted);" title="${escapeHtml(e.timestamp || '')}">${timeAgo(e.timestamp)}</td>`;
        html += `<td style="padding: 6px; font-size: 0.78rem;">${actor}</td>`;
        html += `<td style="padding: 6px; font-family: monospace; font-size: 0.78rem;">${escapeHtml(e.ip || '-')}</td>`;
        html += `<td style="padding: 6px; font-weight: 500;">${escapeHtml(e.action || '-')}</td>`;
        html += `<td style="padding: 6px;">${escapeHtml(e.resource || '-')}</td>`;
        html += `<td style="padding: 6px;"><span style="color: ${ok ? 'var(--ok-fg)' : 'var(--bad-fg)'};">${ok ? '✓' : '✗'} ${escapeHtml(e.outcome || '')}</span></td>`;
        html += '</tr>';
        if (e.details && Object.keys(e.details).length > 0) {
          html += `<tr class="audit-detail" style="display: none;"><td colspan="6" style="padding: 6px 6px 10px; font-size: 0.78rem; color: var(--muted);"><pre style="margin: 0; white-space: pre-wrap; font-family: monospace;">${escapeHtml(JSON.stringify(e.details, null, 2))}</pre></td></tr>`;
        }
      }

      if (!append) {
        html += '</table>';
        container.innerHTML = html;
      } else {
        const table = container.querySelector('table');
        if (table) table.insertAdjacentHTML('beforeend', html);
      }

      currentOffset += entries.length;
      // hasMore is reported by the server (post-filter total), so the
      // Load More button stays accurate when filters change mid-scroll.
      loadMoreBtn.style.display = data.hasMore ? '' : 'none';

      // Toggle detail rows on click
      container.querySelectorAll('.audit-row').forEach(row => {
        if (row.dataset.wired) return;
        row.dataset.wired = 'true';
        row.addEventListener('click', () => {
          const detail = row.nextElementSibling;
          if (detail && detail.classList.contains('audit-detail')) {
            detail.style.display = detail.style.display === 'none' ? '' : 'none';
          }
        });
      });
    } catch (e) {
      // AbortError is expected when we deliberately cancel an in-flight
      // request (e.g. the operator changed filters mid-fetch) — don't
      // flash a "Failed: The user aborted a request" message over the
      // loading spinner. The new fetch has already kicked off.
      if (e && e.name === 'AbortError') return;
      container.innerHTML = `<div class="panel-empty" style="color: var(--bad-fg);">Failed: ${escapeHtml(e.message)}</div>`;
    }
  }

  // Render the human-readable actor: prefer userEmail, fall back to
  // userId, fall back to bare IP. If no user attribution, mark as
  // "system" so the operator knows the entry came from an unauthenticated
  // or service path (e.g. cron-driven backups).
  function actorLabel(entry) {
    const d = entry.details || {};
    const email = d.userEmail;
    const id = d.userId;
    const role = d.userRole;
    const provider = d.viaProvider;
    if (email) {
      const tag = role ? ` <span style="color: var(--muted); font-size: 0.72rem;">[${escapeHtml(role)}${provider ? '/' + escapeHtml(provider) : ''}]</span>` : '';
      return `${escapeHtml(email)}${tag}`;
    }
    if (id) return `<span style="font-family: monospace; color: var(--muted);">${escapeHtml(id)}</span>`;
    if (!entry.ip) return '<span style="color: var(--muted);">system</span>';
    return '<span style="color: var(--muted);">anon</span>';
  }

  openBtn?.addEventListener('click', () => {
    modal?.classList.add('show');
    loadAudit(false);
  });
  wireModal(modal, cancelBtn);
  refreshBtn?.addEventListener('click', () => loadAudit(false));
  filterSelect?.addEventListener('change', () => loadAudit(false));
  outcomeSelect?.addEventListener('change', () => loadAudit(false));
  // Re-fetch on date change only when both fields have a value or both are
  // empty — typing one character shouldn't trigger a fetch for every keystroke.
  let dateDebounce;
  [sinceInput, untilInput].forEach((el) => {
    el?.addEventListener('change', () => {
      clearTimeout(dateDebounce);
      dateDebounce = setTimeout(() => loadAudit(false), 250);
    });
  });
  loadMoreBtn?.addEventListener('click', () => loadAudit(true));

  clearBtn?.addEventListener('click', async () => {
    if (!confirm('Clear the entire audit log? This cannot be undone.')) return;
    try {
      const res = await secureFetch('/api/v1/audit-logs', {
        method: 'DELETE',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ confirm: 'CLEAR' }),
      });
      const data = await res.json();
      if (data.success) loadAudit(false);
      else showNotification('Error: ' + (data.error || 'Clear failed'), 'error');
    } catch (e) {
      showNotification('Error: ' + e.message, 'error');
    }
  });
})();