<script lang="ts">
  import { pageTitle } from '$lib/branding';
  import { Copy, Download, Activity } from 'lucide-svelte';
  import { useQueryClient } from '@tanstack/svelte-query';
  import {
    createAdminDiagnosticsQuery,
    createDiagnosticDevicesQuery,
    createAdminUsersQuery,
    updateUserDebugMode,
    type AdminDiagnostic,
    type DiagnosticsFilter
  } from '$lib/api/admin';
  import { qk } from '$lib/api/queries';
  import { errMessage } from '$lib/api/errors';
  import { pushToast } from '$lib/stores/toast.svelte';

  // Device diagnostics timeline (M9). The operator enables debug-mode on
  // an account (remotely, here), then watches the account's device(s)
  // stream connectivity / UPnP-sync / power events. The point of the page
  // is the EXPORT: filter to the window in question and copy/download the
  // slice as JSON to hand off for analysis.

  const client = useQueryClient();

  const KINDS = [
    'connectivity',
    'playback',
    'upnp_sync',
    'power',
    'lifecycle',
    'heartbeat',
    'http'
  ] as const;

  function kindLabel(k: string): string {
    switch (k) {
      case 'connectivity': return 'Connectivity';
      case 'playback':     return 'Playback';
      case 'upnp_sync':    return 'UPnP sync';
      case 'power':        return 'Power';
      case 'lifecycle':    return 'Lifecycle';
      case 'heartbeat':    return 'Heartbeat';
      case 'http':         return 'HTTP';
      default:             return k;
    }
  }

  // Default view = the most recent N events, no time window. The
  // start/end window is an Advanced affordance.
  const DEFAULT_LIMIT = 500;

  // Filter state.
  let accountId = $state('');
  let clientId = $state('');
  let kind = $state('');
  let fromLocal = $state('');
  let toLocal = $state('');
  let limit = $state(DEFAULT_LIMIT);

  // True when an explicit time window is set (drives the caption wording).
  const hasWindow = $derived(Boolean(fromLocal || toLocal));

  function resetWindow() {
    fromLocal = '';
    toLocal = '';
    limit = DEFAULT_LIMIT;
  }

  // datetime-local (browser-local, no tz) → RFC3339 UTC the API accepts.
  function toRfc(v: string): string | undefined {
    if (!v) return undefined;
    const d = new Date(v);
    return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
  }

  const filter = $derived<DiagnosticsFilter>({
    userId: accountId || undefined,
    clientId: clientId || undefined,
    kind: kind || undefined,
    from: toRfc(fromLocal),
    to: toRfc(toLocal),
    limit
  });

  const usersStore = $derived(createAdminUsersQuery());
  const usersQuery = $derived($usersStore);
  const users = $derived(usersQuery.data ?? []);
  const selectedUser = $derived(users.find((u) => u.id === accountId));

  const devicesStore = $derived(createDiagnosticDevicesQuery(accountId || undefined));
  const devicesQuery = $derived($devicesStore);
  const devices = $derived(devicesQuery.data ?? []);

  // Display sort. The API returns newest-first; default the view to that
  // (most recent at the top), with an Oldest-first option for reading a
  // timeline top-to-bottom. Export follows whatever's displayed.
  let sortOrder = $state<'newest' | 'oldest'>('newest');

  const diagStore = $derived(createAdminDiagnosticsQuery(filter));
  const diagQuery = $derived($diagStore);
  const rows = $derived.by(() => {
    const data = [...((diagQuery.data ?? []) as AdminDiagnostic[])]; // newest-first
    return sortOrder === 'oldest' ? data.reverse() : data;
  });

  function fmtTime(iso: string): string {
    const d = new Date(iso);
    return Number.isNaN(d.getTime()) ? iso : d.toLocaleString();
  }

  function payloadPreview(p: Record<string, unknown>): string {
    const s = JSON.stringify(p);
    return s.length > 120 ? s.slice(0, 120) + '…' : s;
  }

  // Toggle the selected account's debug-mode remotely.
  let toggling = $state(false);
  async function toggleDebug() {
    if (!selectedUser) return;
    toggling = true;
    try {
      await updateUserDebugMode(selectedUser.id, !selectedUser.debug_mode_enabled);
      await client.invalidateQueries({ queryKey: qk.adminUsers() });
      pushToast(
        `Debug mode ${selectedUser.debug_mode_enabled ? 'disabled' : 'enabled'} for ${selectedUser.username}`
      );
    } catch (e: unknown) {
      pushToast(`Toggle failed: ${errMessage(e)}`, 'error');
    } finally {
      toggling = false;
    }
  }

  // Export the current (chronological) slice as an analysis-ready object.
  function exportObject() {
    return {
      account: selectedUser?.username ?? accountId ?? 'all',
      device: clientId || 'all',
      kind: kind || 'all',
      from: toRfc(fromLocal) ?? null,
      to: toRfc(toLocal) ?? null,
      count: rows.length,
      events: rows.map((r) => ({
        occurred_at: r.occurred_at,
        received_at: r.received_at,
        kind: r.kind,
        client_id: r.client_id,
        app_version: r.app_version,
        os_version: r.os_version,
        payload: r.payload
      }))
    };
  }

  async function onCopyJson() {
    try {
      await navigator.clipboard.writeText(JSON.stringify(exportObject(), null, 2));
      pushToast(`Copied ${rows.length} events to clipboard`);
    } catch (e: unknown) {
      pushToast(`Copy failed: ${errMessage(e)}`, 'error');
    }
  }

  function onDownloadNdjson() {
    const lines = exportObject().events.map((e) => JSON.stringify(e)).join('\n');
    const blob = new Blob([lines], { type: 'application/x-ndjson' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    const who = selectedUser?.username ?? 'all';
    a.href = url;
    a.download = `diagnostics-${who}-${Date.now()}.ndjson`;
    a.click();
    URL.revokeObjectURL(url);
  }
</script>

<svelte:head><title>{pageTitle('Admin · Diagnostics')}</title></svelte:head>

<div class="space-y-4">
  <header>
    <h1 class="font-display text-2xl font-medium text-text-primary">Device diagnostics</h1>
    <p class="text-sm text-text-secondary">
      Enable debug mode on an account to have its device(s) stream a timeseries
      of connectivity, UPnP-sync, and power/Doze events here. Filter to the
      window you care about, then copy or download the slice for analysis.
    </p>
  </header>

  <!-- Account debug-mode control -->
  <section class="rounded-md border border-border p-3">
    <div class="flex flex-wrap items-end gap-3">
      <label class="block text-xs text-text-secondary">
        Account
        <select
          bind:value={accountId}
          class="mt-1 block w-56 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
        >
          <option value="">All accounts</option>
          {#each users as u (u.id)}
            <option value={u.id}>{u.username}{u.debug_mode_enabled ? ' · debug on' : ''}</option>
          {/each}
        </select>
      </label>

      {#if selectedUser}
        <button
          type="button"
          onclick={toggleDebug}
          disabled={toggling}
          class="rounded px-3 py-2 text-sm text-action-fg hover:opacity-90 disabled:opacity-50
            {selectedUser.debug_mode_enabled ? 'bg-action-destructive' : 'bg-action-primary'}"
        >
          {selectedUser.debug_mode_enabled ? 'Disable debug mode' : 'Enable debug mode'}
        </button>
        <span class="inline-flex items-center gap-1 text-xs text-text-muted">
          <Activity size={14} />
          {selectedUser.debug_mode_enabled ? 'Reporting active' : 'Reporting off'}
        </span>
      {/if}
    </div>
  </section>

  <!-- Filters. Default view = most recent {DEFAULT_LIMIT} events, no time
       window; the start/end window lives under Advanced. -->
  <section class="space-y-3">
    <div class="flex flex-wrap items-end gap-3">
      <label class="block text-xs text-text-secondary">
        Device
        <select
          bind:value={clientId}
          class="mt-1 block w-48 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
        >
          <option value="">All devices</option>
          {#each devices as d (d.client_id)}
            <option value={d.client_id}>{d.client_id.slice(0, 12)} · {d.event_count}</option>
          {/each}
        </select>
      </label>
      <label class="block text-xs text-text-secondary">
        Kind
        <select
          bind:value={kind}
          class="mt-1 block w-40 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
        >
          <option value="">All kinds</option>
          {#each KINDS as k (k)}
            <option value={k}>{kindLabel(k)}</option>
          {/each}
        </select>
      </label>
      <label class="block text-xs text-text-secondary">
        Sort
        <select
          bind:value={sortOrder}
          class="mt-1 block w-36 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
        >
          <option value="newest">Newest first</option>
          <option value="oldest">Oldest first</option>
        </select>
      </label>

      <div class="ml-auto flex gap-2">
        <button
          type="button"
          onclick={onCopyJson}
          disabled={rows.length === 0}
          class="inline-flex items-center gap-1.5 rounded border border-border px-3 py-2 text-sm text-text-primary hover:bg-surface-hover disabled:opacity-50"
        >
          <Copy size={15} /> Copy JSON
        </button>
        <button
          type="button"
          onclick={onDownloadNdjson}
          disabled={rows.length === 0}
          class="inline-flex items-center gap-1.5 rounded border border-border px-3 py-2 text-sm text-text-primary hover:bg-surface-hover disabled:opacity-50"
        >
          <Download size={15} /> Download
        </button>
      </div>
    </div>

    <!-- Advanced: explicit start/end window + row cap. Collapsed by
         default so the common case (recent N) needs no interaction. -->
    <details class="rounded-md border border-border" open={hasWindow}>
      <summary class="cursor-pointer px-3 py-2 text-xs text-text-secondary">
        Advanced filters{hasWindow ? ' · window active' : ''}
      </summary>
      <div class="flex flex-wrap items-end gap-3 border-t border-border px-3 py-3">
        <label class="block text-xs text-text-secondary">
          From
          <input
            type="datetime-local"
            bind:value={fromLocal}
            class="mt-1 block rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
          />
        </label>
        <label class="block text-xs text-text-secondary">
          To
          <input
            type="datetime-local"
            bind:value={toLocal}
            class="mt-1 block rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
          />
        </label>
        <label class="block text-xs text-text-secondary">
          Limit
          <input
            type="number"
            min="1"
            max="5000"
            bind:value={limit}
            class="mt-1 block w-24 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
          />
        </label>
        <button
          type="button"
          onclick={resetWindow}
          class="rounded border border-border px-3 py-2 text-sm text-text-secondary hover:bg-surface-hover"
        >
          Reset to recent {DEFAULT_LIMIT}
        </button>
      </div>
    </details>
  </section>

  <!-- Timeline -->
  {#if diagQuery.isError}
    <p class="text-error">Couldn't load: {errMessage(diagQuery.error)}</p>
  {:else if diagQuery.isPending}
    <p class="text-text-secondary">Loading…</p>
  {:else if rows.length === 0}
    <p class="text-text-secondary">
      No events for this filter. Enable debug mode on an account and have the
      device reproduce the issue — events appear here within a minute.
    </p>
  {:else}
    <div class="flex items-center justify-between">
      <span class="text-xs text-text-muted">
        {#if hasWindow}
          {rows.length} events in window ({sortOrder === 'newest' ? 'newest' : 'oldest'} first)
        {:else}
          Most recent {rows.length} events ({sortOrder === 'newest' ? 'newest' : 'oldest'} first) · set a time window under Advanced
        {/if}
      </span>
    </div>
    <ul class="divide-y divide-border rounded-md border border-border font-mono text-xs">
      {#each rows as r (r.id)}
        <li class="flex items-start gap-3 px-3 py-2">
          <span class="w-44 shrink-0 text-text-muted" title={`received ${fmtTime(r.received_at)}`}>
            {fmtTime(r.occurred_at)}
          </span>
          <span
            class="w-24 shrink-0 rounded bg-surface-hover px-1.5 py-0.5 text-center text-[10px] uppercase tracking-wide text-text-secondary"
          >
            {kindLabel(r.kind)}
          </span>
          <details class="min-w-0 flex-1">
            <summary class="cursor-pointer truncate text-text-primary">
              {payloadPreview(r.payload)}
            </summary>
            <pre class="mt-1 overflow-x-auto whitespace-pre-wrap text-text-secondary">{JSON.stringify(r.payload, null, 2)}</pre>
          </details>
        </li>
      {/each}
    </ul>
  {/if}
</div>
