/* /admin/integrations — operator-managed integration toggles.
 *
 * Joins `integration_settings` (DB-backed disabled state, audit info)
 * with `/api/dashboard/integration-health` (live status + categories +
 * display names) so the admin sees both halves on one page.  Toggling a
 * row hits PATCH /api/admin/integrations and forces a health refresh. */

'use client';

import { useEffect, useState } from 'react';
import { PageHeader } from '@/components/navigation/page-header';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { Input } from '@/components/ui/input';
import { Skeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { StatusLight, type StatusLightState } from '@/components/ui/status-light';
import { StatusBadge } from '@/components/ui/status-badge';
import { EmptyState } from '@/components/ui/empty-state';
import { RefreshCw, AlertTriangle, Power } from 'lucide-react';
import { toast } from 'sonner';

interface IntegrationSetting {
  key: string;
  disabled: boolean;
  reason: string | null;
  disabledBy: string | null;
  disabledAt: string | null;
  updatedAt: string;
}

interface IntegrationHealthItem {
  key: string;
  name: string;
  category: string;
  status: 'ok' | 'auth_failed' | 'unreachable' | 'not_configured' | 'unknown' | 'disabled';
  configured: boolean;
  latencyMs?: number;
  error?: string | null;
  tokenExpiry?: { daysRemaining: number } | null;
}

interface MergedRow {
  key: string;
  name: string;
  category: string;
  liveStatus: IntegrationHealthItem['status'];
  setting: IntegrationSetting;
}

const CATEGORY_LABELS: Record<string, string> = {
  psa: 'PSA',
  rmm: 'RMM',
  docs: 'Documentation',
  security: 'Security',
  backup: 'Backup',
  network: 'Network',
  identity: 'Identity',
  mdm: 'MDM',
  mail: 'Mail',
  finance: 'Finance',
  productivity: 'Productivity',
  llm: 'LLM',
};

const CATEGORY_ORDER = [
  'psa', 'rmm', 'docs', 'security', 'backup',
  'network', 'identity', 'mdm', 'mail',
  'finance', 'productivity', 'llm',
];

function liveStatusLight(s: IntegrationHealthItem['status']): StatusLightState {
  if (s === 'ok') return 'ok';
  if (s === 'auth_failed' || s === 'unreachable') return 'error';
  if (s === 'disabled') return 'idle';
  return 'idle';
}

function fmtDate(iso: string | null): string {
  if (!iso) return '—';
  const ms = Date.now() - new Date(iso).getTime();
  if (ms < 60_000) return 'just now';
  const min = Math.floor(ms / 60_000);
  if (min < 60) return `${min} min ago`;
  const hr = Math.floor(min / 60);
  if (hr < 48) return `${hr} h ago`;
  return `${Math.floor(hr / 24)} d ago`;
}

const ENV_OVERRIDE_NOTE =
  'INTEGRATIONS_DISABLED env var is also active — env entries always take precedence and cannot be re-enabled here.';

export default function IntegrationTogglesPage() {
  const [rows, setRows] = useState<MergedRow[] | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [pending, setPending] = useState<string | null>(null);
  const [reasons, setReasons] = useState<Record<string, string>>({});

  async function load() {
    setLoading(true);
    try {
      const [sRes, hRes] = await Promise.all([
        fetch('/api/admin/integrations', { cache: 'no-store' }),
        fetch('/api/dashboard/integration-health', { cache: 'no-store' }),
      ]);
      if (!sRes.ok) throw new Error('Failed to load integration settings');
      if (!hRes.ok) throw new Error('Failed to load integration health');
      const sBody = (await sRes.json()) as { items: IntegrationSetting[] };
      const hBody = (await hRes.json()) as { items: IntegrationHealthItem[] };

      const settingByKey = new Map(sBody.items.map((s) => [s.key, s]));

      // Source of truth for the row list is the live integration-health
      // response (it carries display names + categories).  We merge in the
      // setting if one exists, otherwise synthesize a default.
      const merged: MergedRow[] = hBody.items.map((h) => ({
        key: h.key,
        name: h.name,
        category: h.category,
        liveStatus: h.status,
        setting:
          settingByKey.get(h.key) ??
          {
            key: h.key,
            disabled: h.status === 'disabled',
            reason: null,
            disabledBy: null,
            disabledAt: null,
            updatedAt: '',
          },
      }));
      merged.sort((a, b) => a.name.localeCompare(b.name));
      setRows(merged);
      setError(null);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load');
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void load();
  }, []);

  async function toggle(row: MergedRow, next: boolean) {
    setPending(row.key);
    try {
      const res = await fetch('/api/admin/integrations', {
        method: 'PATCH',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          key: row.key,
          disabled: next,
          reason: next ? reasons[row.key] || null : null,
        }),
      });
      if (!res.ok) {
        const body = (await res.json().catch(() => ({}))) as { error?: string };
        throw new Error(body.error ?? `HTTP ${res.status}`);
      }
      toast.success(`${row.name} ${next ? 'disabled' : 'enabled'}`);
      // Reload merged view so live status reflects the change after cache flush.
      await load();
      // Clear the inline reason input on success.
      setReasons((prev) => {
        const copy = { ...prev };
        delete copy[row.key];
        return copy;
      });
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Toggle failed');
    } finally {
      setPending(null);
    }
  }

  const disabledCount = rows?.filter((r) => r.setting.disabled).length ?? 0;
  const totalCount = rows?.length ?? 0;

  return (
    <>
      <PageHeader
        title="Integrations"
        description={
          rows
            ? `${disabledCount} of ${totalCount} disabled`
            : 'Toggle integrations on or off without a container restart'
        }
        breadcrumbs={[
          { label: 'Admin', href: '/admin' },
          { label: 'Integrations' },
        ]}
        accent
        actions={
          <Button onClick={load} variant="outline" size="sm" disabled={loading}>
            <RefreshCw className={`h-4 w-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
            Refresh
          </Button>
        }
      />

      <div className="container mx-auto px-6 py-6 space-y-6 max-w-4xl">
        {error && (
          <Alert variant="destructive">
            <AlertTitle>Failed to load</AlertTitle>
            <AlertDescription>{error}</AlertDescription>
          </Alert>
        )}

        <Alert>
          <AlertTriangle className="h-4 w-4" />
          <AlertTitle>How this works</AlertTitle>
          <AlertDescription className="space-y-1 text-sm">
            <p>
              Disabling an integration here suppresses it from <code>/status</code> and
              the top-bar status light, and excludes it from failure roll-ups. Live
              auth checks still run (so the underlying state is logged), but the UI
              ignores them.
            </p>
            <p className="text-muted-foreground">{ENV_OVERRIDE_NOTE}</p>
          </AlertDescription>
        </Alert>

        <Card>
          <CardHeader className="pb-3">
            <CardTitle className="text-base flex items-center gap-2">
              <Power className="h-4 w-4" />
              Toggle integrations
            </CardTitle>
          </CardHeader>
          <CardContent className="p-0">
            {!rows ? (
              <div className="p-6 space-y-3">
                {[1, 2, 3, 4, 5].map((i) => (
                  <Skeleton key={i} className="h-14" />
                ))}
              </div>
            ) : rows.length === 0 ? (
              <div className="p-6">
                <EmptyState
                  icon={Power}
                  title="No integrations registered"
                  description="The integration-health service didn't return any items."
                  size="sm"
                />
              </div>
            ) : (
              (() => {
                // Bucket rows by category, preserving the canonical order.
                const byCategory = new Map<string, MergedRow[]>();
                for (const r of rows) {
                  const arr = byCategory.get(r.category) ?? [];
                  arr.push(r);
                  byCategory.set(r.category, arr);
                }
                const visibleCategories = CATEGORY_ORDER.filter((c) => byCategory.has(c));
                // Catch any unexpected category not in the canonical order.
                for (const c of byCategory.keys()) {
                  if (!visibleCategories.includes(c)) visibleCategories.push(c);
                }

                return (
                  <div className="divide-y divide-border">
                    {visibleCategories.map((category) => (
                      <div key={category}>
                        <p className="metric-label px-4 pt-3 pb-1">
                          {CATEGORY_LABELS[category] ?? category}
                        </p>
                        <ul className="divide-y divide-border/50">
                          {byCategory.get(category)!
                            .slice()
                            .sort((a, b) => a.name.localeCompare(b.name))
                            .map((row) => (
                              <IntegrationRow
                                key={row.key}
                                row={row}
                                pending={pending === row.key}
                                reasonValue={reasons[row.key] ?? ''}
                                onReasonChange={(v) =>
                                  setReasons((prev) => ({ ...prev, [row.key]: v }))
                                }
                                onToggle={(next) => void toggle(row, next)}
                              />
                            ))}
                        </ul>
                      </div>
                    ))}
                  </div>
                );
              })()
            )}
          </CardContent>
        </Card>
      </div>
    </>
  );
}

function IntegrationRow({
  row,
  pending,
  reasonValue,
  onReasonChange,
  onToggle,
}: {
  row: MergedRow;
  pending: boolean;
  reasonValue: string;
  onReasonChange: (v: string) => void;
  onToggle: (next: boolean) => void;
}) {
  const isDisabled = row.setting.disabled;
  return (
    <li className={`grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3 px-4 py-3 ${isDisabled ? 'opacity-80' : ''}`}>
      <div className="flex items-start gap-3 min-w-0">
        <StatusLight state={liveStatusLight(row.liveStatus)} size="md" className="mt-1.5" label={row.liveStatus} />
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2 flex-wrap">
            <span className="font-medium">{row.name}</span>
            <span className="text-xs text-muted-foreground uppercase tracking-wide">
              {row.category}
            </span>
            {isDisabled && <StatusBadge tone="inactive" size="xs">disabled</StatusBadge>}
          </div>
          <p className="text-xs text-muted-foreground mt-0.5">
            <span className="num">{row.key}</span>
            {row.setting.disabledBy && (
              <>
                {' · disabled by '}
                <span>{row.setting.disabledBy}</span>
                {' '}
                <span className="num">{fmtDate(row.setting.disabledAt)}</span>
              </>
            )}
          </p>
          {isDisabled && row.setting.reason && (
            <p className="text-xs italic text-muted-foreground mt-1">
              "{row.setting.reason}"
            </p>
          )}
          {!isDisabled && (
            <Input
              placeholder="Optional: why are you disabling this?"
              value={reasonValue}
              onChange={(e) => onReasonChange(e.target.value)}
              className="h-7 text-xs mt-2 max-w-md"
              disabled={pending}
            />
          )}
        </div>
      </div>
      <div className="flex items-center justify-end gap-2 md:self-center">
        <Switch
          checked={!isDisabled}
          onCheckedChange={(v) => onToggle(!v)}
          disabled={pending}
          aria-label={`Toggle ${row.name}`}
        />
        <span className="text-xs text-muted-foreground w-14 text-left">
          {isDisabled ? 'Disabled' : 'Enabled'}
        </span>
      </div>
    </li>
  );
}
