'use client';

import { useState } from 'react';
import { CheckCircle, XCircle, Loader2, PlayCircle, AlertTriangle } from 'lucide-react';

/**
 * In-dashboard smoke tester for the public REST API.
 *
 * Takes a Bearer key (paste it from the visible-once banner above), then
 * exercises every v1 endpoint and shows the per-endpoint HTTP status. The
 * goal is to give integration partners a one-click "is everything working
 * for this key?" probe — covering auth, permissions, the standard error
 * envelope, and the OpenAPI spec.
 *
 * Tests are split into:
 *   - read tests (GET / list endpoints) → expected 2xx
 *   - lookup tests (GET /:id with a synthetic uuid) → expected 4xx, proves
 *     the route exists and auth/perm passes (404 on unknown id)
 *   - validation probes (POST/PATCH/DELETE with empty body) → expected 4xx
 *     with the v1 error envelope, proves the envelope is wired
 *   - the public OpenAPI spec → expected 200
 *
 * We deliberately do NOT POST real data because this runs against the live
 * tenant. The validation probes verify the route is mounted and protected
 * without creating phantom orders/bookings.
 */

type Probe = {
  label: string;
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
  path: string;
  body?: unknown;
  /** Status codes that count as "the endpoint is healthy for this key". */
  expect: (status: number) => boolean;
  /** Description of what we're verifying — surfaced in UI so users know why
   * a 4xx counts as success here. */
  hint: string;
  /** When true, omits the Authorization header so we can verify 401 path. */
  noAuth?: boolean;
};

const SYNTH_ID = '00000000-0000-0000-0000-000000000000';

const PROBES: Probe[] = [
  { label: 'OpenAPI spec',                method: 'GET', path: '/api/v1/openapi.json',                            expect: (s) => s === 200, hint: 'Spec served publicly' },
  { label: 'Auth required (no token)',    method: 'GET', path: '/api/v1/orders',                                  expect: (s) => s === 401, hint: 'Returns 401 envelope when token missing', noAuth: true },
  { label: 'List orders',                 method: 'GET', path: '/api/v1/orders?limit=1',                          expect: (s) => s === 200, hint: 'orders:read works' },
  { label: 'Get order (synthetic id)',    method: 'GET', path: `/api/v1/orders/${SYNTH_ID}`,                      expect: (s) => s === 404, hint: 'Route mounted, not-found envelope returned' },
  { label: 'List bookings',               method: 'GET', path: '/api/v1/bookings?limit=1',                        expect: (s) => s === 200, hint: 'bookings:read works' },
  { label: 'List customers',              method: 'GET', path: '/api/v1/customers?limit=1',                       expect: (s) => s === 200, hint: 'customers:read works' },
  { label: 'Get customer (synthetic id)', method: 'GET', path: `/api/v1/customers/${SYNTH_ID}`,                   expect: (s) => s === 404, hint: 'Route mounted' },
  { label: 'List menu items',             method: 'GET', path: '/api/v1/menu/items?limit=1',                      expect: (s) => s === 200, hint: 'menu:read works' },
  { label: 'Analytics summary',           method: 'GET', path: '/api/v1/analytics/summary?period=today',          expect: (s) => s === 200, hint: 'analytics:read works' },
  { label: 'List AI conversations',       method: 'GET', path: '/api/v1/ai/conversations?limit=1',                expect: (s) => s === 200, hint: 'ai:read works' },
  // Write-side validation probes: empty bodies trigger v1 envelope 400s.
  { label: 'Create order — validation',     method: 'POST',   path: '/api/v1/orders',          body: {},                              expect: (s) => s === 400, hint: 'orders:write reachable, envelope returned' },
  { label: 'Update order — validation',     method: 'PATCH',  path: `/api/v1/orders/${SYNTH_ID}`, body: { status: 'not-a-status' },   expect: (s) => s >= 400 && s < 500, hint: 'PATCH route mounted' },
  { label: 'Create booking — validation',   method: 'POST',   path: '/api/v1/bookings',        body: {},                              expect: (s) => s === 400, hint: 'bookings:write reachable' },
  { label: 'Cancel booking (synthetic id)', method: 'DELETE', path: `/api/v1/bookings/${SYNTH_ID}`,                                   expect: (s) => s === 404, hint: 'DELETE route mounted' },
  { label: 'Create menu item — validation', method: 'POST',   path: '/api/v1/menu/items',      body: {},                              expect: (s) => s === 400, hint: 'menu:write reachable' },
  { label: 'Takeover (synthetic id)',       method: 'POST',   path: `/api/v1/ai/conversations/${SYNTH_ID}/takeover`, body: { agent_name: 'smoke' }, expect: (s) => s === 404 || s === 200, hint: 'ai:write reachable' },
];

type Result = {
  status: number;
  ok: boolean;
  envelopeOk: boolean;
  message?: string;
  durationMs: number;
};

export default function SmokeTester() {
  const [token, setToken] = useState('');
  const [running, setRunning] = useState(false);
  const [results, setResults] = useState<Record<number, Result | 'pending'>>({});

  const run = async () => {
    if (!token.trim() || running) return;
    setRunning(true);
    setResults({});

    for (let i = 0; i < PROBES.length; i++) {
      const p = PROBES[i];
      setResults((prev) => ({ ...prev, [i]: 'pending' }));
      const started = performance.now();
      try {
        const res = await fetch(p.path, {
          method: p.method,
          headers: {
            ...(p.noAuth ? {} : { Authorization: `Bearer ${token.trim()}` }),
            ...(p.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
          },
          body: p.body !== undefined ? JSON.stringify(p.body) : undefined,
        });
        const duration = Math.round(performance.now() - started);
        let envelopeOk = true;
        let message: string | undefined;
        // For non-2xx responses, validate that the body matches the public
        // envelope shape. For OpenAPI we only care about the http status.
        if (res.status >= 400 && p.path !== '/api/v1/openapi.json') {
          try {
            const body = await res.clone().json();
            envelopeOk =
              !!body &&
              typeof body === 'object' &&
              !!body.error &&
              typeof body.error.code === 'string' &&
              typeof body.error.message === 'string';
            message = body?.error?.message;
          } catch {
            envelopeOk = false;
            message = 'Non-JSON error body';
          }
        }
        setResults((prev) => ({
          ...prev,
          [i]: {
            status: res.status,
            ok: p.expect(res.status) && envelopeOk,
            envelopeOk,
            message,
            durationMs: duration,
          },
        }));
      } catch (e) {
        setResults((prev) => ({
          ...prev,
          [i]: {
            status: 0,
            ok: false,
            envelopeOk: false,
            message: e instanceof Error ? e.message : String(e),
            durationMs: Math.round(performance.now() - started),
          },
        }));
      }
    }
    setRunning(false);
  };

  const completed = Object.values(results).filter((r) => r !== 'pending') as Result[];
  const passCount = completed.filter((r) => r.ok).length;
  const failCount = completed.filter((r) => !r.ok).length;

  return (
    <div className="rounded-2xl mb-6" style={{ backgroundColor: 'hsl(var(--surface))', border: '1px solid hsl(var(--border))' }}>
      <div className="p-5 border-b" style={{ borderColor: 'hsl(var(--border))' }}>
        <div className="flex items-center gap-2 mb-1">
          <PlayCircle size={16} style={{ color: 'hsl(var(--primary))' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'hsl(var(--foreground))' }}>API Smoke Test</h3>
          {completed.length > 0 && (
            <span className="text-xs font-semibold ml-auto" style={{ color: failCount > 0 ? 'hsl(0, 84%, 44%)' : 'hsl(142, 72%, 29%)' }}>
              {passCount}/{PROBES.length} passing
            </span>
          )}
        </div>
        <p className="text-xs mb-3" style={{ color: 'hsl(var(--muted-foreground))' }}>
          Paste an API key to exercise every v1 endpoint with this key. Read endpoints expect 200, write endpoints are probed with empty bodies and expected to return a 4xx envelope (no real data is created).
        </p>
        <div className="flex items-center gap-2">
          <input
            type="text"
            placeholder="rsk_…"
            value={token}
            onChange={(e) => setToken(e.target.value)}
            className="input-field flex-1 font-mono text-xs"
            autoComplete="off"
            spellCheck={false}
          />
          <button
            className="btn-primary text-sm"
            onClick={run}
            disabled={!token.trim() || running}
          >
            {running ? <Loader2 size={14} className="animate-spin" /> : <PlayCircle size={14} />}
            {running ? 'Running…' : 'Run smoke test'}
          </button>
        </div>
      </div>

      {Object.keys(results).length > 0 && (
        <div>
          {PROBES.map((p, i) => {
            const r = results[i];
            const pending = r === 'pending' || r === undefined;
            const result = pending ? null : (r as Result);
            const ok = !!result?.ok;
            return (
              <div
                key={i}
                className="px-5 py-2.5 flex items-center gap-3 text-xs border-t"
                style={{ borderColor: 'hsl(var(--border))' }}
              >
                <div className="w-5 shrink-0">
                  {pending ? (
                    r === 'pending' ? <Loader2 size={14} className="animate-spin" style={{ color: 'hsl(var(--muted-foreground))' }} /> : <span className="opacity-30">·</span>
                  ) : ok ? (
                    <CheckCircle size={14} style={{ color: 'hsl(142, 72%, 29%)' }} />
                  ) : result && !result.envelopeOk && result.status >= 400 ? (
                    <AlertTriangle size={14} style={{ color: 'hsl(38, 92%, 40%)' }} />
                  ) : (
                    <XCircle size={14} style={{ color: 'hsl(0, 84%, 44%)' }} />
                  )}
                </div>
                <span
                  className="font-mono font-bold w-14 shrink-0 text-[10px] px-1.5 py-0.5 rounded text-center"
                  style={{
                    backgroundColor: 'hsl(var(--muted))',
                    color: 'hsl(var(--foreground-secondary))',
                  }}
                >
                  {p.method}
                </span>
                <span className="font-medium shrink-0" style={{ color: 'hsl(var(--foreground))' }}>{p.label}</span>
                <code className="font-mono truncate text-[11px]" style={{ color: 'hsl(var(--muted-foreground))' }}>{p.path}</code>
                <span className="ml-auto flex items-center gap-3 shrink-0" style={{ color: 'hsl(var(--muted-foreground))' }}>
                  {result && (
                    <>
                      <span className="text-[11px]">{result.durationMs}ms</span>
                      <span
                        className="font-mono font-semibold text-[11px] px-1.5 py-0.5 rounded"
                        style={{
                          backgroundColor: ok ? 'hsl(142, 72%, 96%)' : 'hsl(0, 84%, 97%)',
                          color: ok ? 'hsl(142, 72%, 29%)' : 'hsl(0, 84%, 44%)',
                        }}
                      >
                        {result.status || 'ERR'}
                      </span>
                    </>
                  )}
                </span>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}
