import { useState } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { SshConnection, TestResponse } from '../../lib/ssh-types';
import { SshConnectionForm } from './SshConnectionForm';
import { SshHostKeyDialog } from './SshHostKeyDialog';
import { SshPublicKeyDialog } from './SshPublicKeyDialog';

interface ConnectionsResponse {
  connections: SshConnection[];
}

interface CreateResponse {
  connection: SshConnection;
  publicKey?: string | null;
}

async function fetchConnections(): Promise<{ list: SshConnection[]; sshDisabled: boolean }> {
  const res = await fetch('/api/ssh/connections', { credentials: 'include' });
  if (res.status === 404) {
    return { list: [], sshDisabled: true };
  }
  if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
  const data: ConnectionsResponse = await res.json();
  return { list: data.connections ?? [], sshDisabled: false };
}

async function apiCreate(body: Record<string, unknown>): Promise<CreateResponse> {
  const res = await fetch('/api/ssh/connections', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
  return (await res.json()) as CreateResponse;
}

async function apiGetPublicKey(id: string): Promise<string | null> {
  const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}`, {
    credentials: 'include',
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
  const json = (await res.json()) as { publicKey?: string | null };
  return json.publicKey ?? null;
}

async function apiPatch(id: string, body: Record<string, unknown>): Promise<SshConnection> {
  const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}`, {
    method: 'PATCH',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
  const json = (await res.json()) as { connection: SshConnection };
  return json.connection;
}

async function apiDelete(id: string): Promise<void> {
  const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}`, {
    method: 'DELETE',
    credentials: 'include',
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
}

async function apiTest(id: string): Promise<TestResponse> {
  const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}/test`, {
    method: 'POST',
    credentials: 'include',
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
  return (await res.json()) as TestResponse;
}

async function apiVerifyHostKey(
  id: string,
  body: { fingerprint: string; token: string; reason?: string },
): Promise<void> {
  const endpoint = body.reason ? 'replace-host-key' : 'verify-host-key';
  const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}/${endpoint}`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const txt = await res.text();
    throw new Error(parseApiError(txt, res.status));
  }
}

function parseApiError(rawText: string, status: number): string {
  try {
    const parsed = JSON.parse(rawText);
    if (parsed?.error) {
      return parsed.detail ? `${parsed.error}: ${typeof parsed.detail === 'string' ? parsed.detail : JSON.stringify(parsed.detail)}` : parsed.error;
    }
  } catch {
    // ignore
  }
  return `HTTP ${status}`;
}

interface SshConnectionsPanelProps {
  /** Render personal+globals (user mode) or only globals via admin endpoints. */
  scope?: 'user';
  showToast?: (msg: string, variant?: 'success' | 'error') => void;
}

export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}) {
  const { t } = useTranslation('userfolder');
  const qc = useQueryClient();
  const { data, isLoading, error } = useQuery({
    queryKey: ['ssh', 'connections'],
    queryFn: fetchConnections,
    staleTime: 15_000,
  });

  const [creating, setCreating] = useState(false);
  const [editingId, setEditingId] = useState<string | null>(null);
  const [testResult, setTestResult] = useState<{ id: string; test: TestResponse; replaceMode: boolean } | null>(null);
  const [pubKeyDialog, setPubKeyDialog] = useState<{
    publicKey: string;
    label?: string;
    freshlyGenerated: boolean;
  } | null>(null);

  const createMutation = useMutation({
    mutationFn: apiCreate,
    onSuccess: (resp) => {
      qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
      setCreating(false);
      showToast?.(t('ssh.toast.created'), 'success');
      // If the server returned a public key (always for keypairSource=generate;
      // also surfaced for provided keys), open the dialog so the user can
      // copy it into authorized_keys.
      if (resp.publicKey) {
        setPubKeyDialog({
          publicKey: resp.publicKey,
          label: resp.connection.label,
          freshlyGenerated: true,
        });
      }
    },
  });
  const showPubKeyMutation = useMutation({
    mutationFn: async ({ id, label }: { id: string; label: string }) => {
      const pk = await apiGetPublicKey(id);
      return { publicKey: pk, label };
    },
    onSuccess: ({ publicKey, label }) => {
      if (publicKey) {
        setPubKeyDialog({ publicKey, label, freshlyGenerated: false });
      } else {
        showToast?.(t('ssh.toast.publicKeyFetchFailed'), 'error');
      }
    },
    onError: (e) => {
      showToast?.(e instanceof Error ? e.message : t('ssh.toast.publicKeyFetchFailedShort'), 'error');
    },
  });
  const patchMutation = useMutation({
    mutationFn: ({ id, body }: { id: string; body: Record<string, unknown> }) => apiPatch(id, body),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
      setEditingId(null);
      showToast?.(t('ssh.toast.updated'), 'success');
    },
  });
  const deleteMutation = useMutation({
    mutationFn: apiDelete,
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
      showToast?.(t('ssh.toast.deleted'), 'success');
    },
    onError: (e) => {
      showToast?.(e instanceof Error ? e.message : t('ssh.toast.deleteFailed'), 'error');
    },
  });
  const testMutation = useMutation({
    mutationFn: apiTest,
    onSuccess: (response, id) => {
      qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
      // Surface result. pass = already verified; first_observe/mismatch = needs confirm.
      if (response.verdict === 'pass') {
        showToast?.(t('ssh.toast.hostKeyMatch', { fingerprint: response.fingerprint.slice(0, 20) }), 'success');
      } else if (response.verdict === 'first_observe' || response.verdict === 'mismatch') {
        setTestResult({ id, test: response, replaceMode: response.verdict === 'mismatch' });
      } else if (response.verdict === 'alg_not_allowed') {
        showToast?.(t('ssh.toast.algNotAllowed'), 'error');
      }
    },
    onError: (e) => {
      showToast?.(e instanceof Error ? e.message : t('ssh.toast.testFailed'), 'error');
    },
  });

  async function handleVerifyHostKey(connId: string, args: { fingerprint: string; token: string; reason?: string }) {
    await apiVerifyHostKey(connId, args);
    qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
    showToast?.(t('ssh.toast.hostKeyVerified'), 'success');
  }

  if (data?.sshDisabled) {
    return (
      <div className="h-full overflow-y-auto">
        <div className="max-w-2xl mx-auto px-6 py-8">
          <h2 className="text-base font-semibold text-slate-900 mb-2">{t('ssh.title')}</h2>
          <div className="text-xs text-slate-600 bg-surface border border-hairline rounded-md p-3 leading-relaxed">
            <Trans t={t} i18nKey="ssh.disabled" components={{ code: <code className="font-mono" /> }} />
          </div>
        </div>
      </div>
    );
  }

  const owned = (data?.list ?? []).filter(c => c.ownerId !== null);
  const globals = (data?.list ?? []).filter(c => c.ownerId === null);

  return (
    <div className="h-full overflow-y-auto">
      <div className="max-w-3xl mx-auto px-6 py-6">
        <div className="flex items-center justify-between mb-4">
          <div>
            <h2 className="text-base font-semibold text-slate-900">{t('ssh.title')}</h2>
            <p className="text-2xs text-slate-500 mt-0.5">
              {t('ssh.intro')}
            </p>
          </div>
          <button
            type="button"
            onClick={() => { setCreating(true); setEditingId(null); }}
            className="px-3 h-7 text-xs font-semibold bg-accent text-accent-fg rounded-md hover:bg-accent-deep"
            disabled={creating}
          >
            {t('ssh.create')}
          </button>
        </div>

        {isLoading && <div className="text-xs text-slate-400">{t('common.loading')}</div>}
        {error && <div className="text-xs text-red-500">{t('ssh.loadFailed', { error: String(error) })}</div>}

        {creating && (
          <section className="mb-5 border border-accent/40 rounded-md bg-canvas p-4">
            <h3 className="text-xs font-semibold text-slate-700 mb-2">{t('ssh.newHeading')}</h3>
            <SshConnectionForm
              existing={null}
              adminContext={false}
              onSubmit={async (body) => { await createMutation.mutateAsync(body); }}
              onCancel={() => setCreating(false)}
            />
          </section>
        )}

        <SectionHeader title={t('ssh.ownSection')} count={owned.length} />
        {owned.length === 0 && !creating && (
          <div className="text-xs text-slate-400 px-3 py-4">
            {t('ssh.ownEmpty')}
          </div>
        )}
        <ul className="divide-y divide-hairline mb-6">
          {owned.map(c => (
            <ConnectionRow
              key={c.id}
              connection={c}
              isOwner
              editing={editingId === c.id}
              onEdit={() => { setEditingId(c.id); setCreating(false); }}
              onCancelEdit={() => setEditingId(null)}
              onPatch={async (body) => { await patchMutation.mutateAsync({ id: c.id, body }); }}
              onDelete={() => {
                if (window.confirm(t('ssh.confirmDelete', { label: c.label }))) {
                  deleteMutation.mutate(c.id);
                }
              }}
              onTest={() => testMutation.mutate(c.id)}
              testing={testMutation.isPending && testMutation.variables === c.id}
              onShowPublicKey={() => showPubKeyMutation.mutate({ id: c.id, label: c.label })}
              showingPublicKey={showPubKeyMutation.isPending && showPubKeyMutation.variables?.id === c.id}
            />
          ))}
        </ul>

        {globals.length > 0 && (
          <>
            <SectionHeader title={t('ssh.globalSection')} count={globals.length} />
            <p className="text-2xs text-slate-500 px-3 mb-2">
              {t('ssh.globalNote')}
            </p>
            <ul className="divide-y divide-hairline">
              {globals.map(c => (
                <ConnectionRow
                  key={c.id}
                  connection={c}
                  isOwner={false}
                  editing={false}
                  onEdit={() => {}}
                  onCancelEdit={() => {}}
                  onPatch={async () => {}}
                  onDelete={() => {}}
                  onTest={() => testMutation.mutate(c.id)}
                  testing={testMutation.isPending && testMutation.variables === c.id}
                  onShowPublicKey={() => showPubKeyMutation.mutate({ id: c.id, label: c.label })}
                  showingPublicKey={showPubKeyMutation.isPending && showPubKeyMutation.variables?.id === c.id}
                />
              ))}
            </ul>
          </>
        )}
      </div>

      {testResult && (
        <SshHostKeyDialog
          test={testResult.test}
          replaceMode={testResult.replaceMode}
          onClose={() => setTestResult(null)}
          onVerify={(args) => handleVerifyHostKey(testResult.id, args)}
        />
      )}

      {pubKeyDialog && (
        <SshPublicKeyDialog
          publicKey={pubKeyDialog.publicKey}
          label={pubKeyDialog.label}
          freshlyGenerated={pubKeyDialog.freshlyGenerated}
          onClose={() => setPubKeyDialog(null)}
        />
      )}
    </div>
  );
}

/** Re-exported for use by other panels that render ConnectionRow. */
export type ShowPublicKeyHandler = (args: { id: string; label: string }) => void;

function SectionHeader({ title, count }: { title: string; count: number }) {
  return (
    <div className="flex items-center gap-2 px-1 py-2 text-2xs font-semibold uppercase tracking-wide text-slate-500">
      <span>{title}</span>
      <span className="text-slate-400 font-mono">({count})</span>
    </div>
  );
}

interface ConnectionRowProps {
  connection: SshConnection;
  isOwner: boolean;
  editing: boolean;
  onEdit: () => void;
  onCancelEdit: () => void;
  onPatch: (body: Record<string, unknown>) => Promise<void>;
  onDelete: () => void;
  onTest: () => void;
  testing: boolean;
  onShowPublicKey: () => void;
  showingPublicKey: boolean;
}

function ConnectionRow(props: ConnectionRowProps) {
  const { t } = useTranslation('userfolder');
  const {
    connection: c, isOwner, editing,
    onEdit, onCancelEdit, onPatch, onDelete, onTest, testing,
    onShowPublicKey, showingPublicKey,
  } = props;
  const verified = c.hostKeyVerifiedAt !== null;
  const pending = c.hostKeyPending;
  const disabled = c.disabledByAdmin || !c.enabled;

  return (
    <li className="py-3">
      <div className="flex items-start justify-between gap-3">
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2 flex-wrap">
            <span className="text-sm font-semibold text-slate-900 truncate">{c.label}</span>
            <ScopeBadge owner={c.ownerId} />
            <HostKeyBadge verified={verified} pending={pending} />
            {disabled && <Badge color="red">{c.disabledByAdmin ? 'admin-disabled' : 'disabled'}</Badge>}
            {c.allowRemoteUnrestricted && <Badge color="amber">remote: unrestricted</Badge>}
            {c.allowPrivateAddresses && <Badge color="amber">private addrs</Badge>}
          </div>
          <div className="text-2xs text-slate-600 font-mono mt-1 truncate">
            {c.username}@{c.host}:{c.port}
          </div>
          <div className="text-2xs text-slate-500 mt-0.5">
            id: <CopyableUuid value={c.id} />
            {' · '}path-prefix: <span className="font-mono">{c.remotePathPrefix}</span>
            {c.keyFingerprint && (
              <>
                {' · '}key fp: <span className="font-mono">{c.keyFingerprint.slice(0, 24)}…</span>
              </>
            )}
          </div>
          {c.disabledByAdminReason && (
            <div className="text-2xs text-red-700 dark:text-red-300 mt-0.5">{t('ssh.row.reasonLabel', { reason: c.disabledByAdminReason })}</div>
          )}
        </div>
        <div className="flex items-center gap-1 flex-shrink-0 flex-wrap justify-end">
          <button
            type="button"
            onClick={onTest}
            disabled={testing}
            className="px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface disabled:opacity-50"
          >
            {testing ? t('ssh.row.testing') : t('ssh.row.test')}
          </button>
          <button
            type="button"
            onClick={onShowPublicKey}
            disabled={showingPublicKey}
            title={t('ssh.row.publicKeyTitle')}
            className="px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface disabled:opacity-50"
          >
            {showingPublicKey ? t('ssh.row.fetching') : t('ssh.row.publicKey')}
          </button>
          {isOwner && !editing && (
            <button
              type="button"
              onClick={onEdit}
              className="px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface"
            >
              {t('ssh.row.edit')}
            </button>
          )}
          {isOwner && (
            <button
              type="button"
              onClick={onDelete}
              className="px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 dark:hover:bg-red-500/15"
            >
              {t('ssh.row.delete')}
            </button>
          )}
        </div>
      </div>
      {editing && (
        <div className="mt-3 ml-1 pl-3 border-l-2 border-accent/30">
          <SshConnectionForm
            existing={c}
            adminContext={false}
            onSubmit={async (body) => { await onPatch(body); }}
            onCancel={onCancelEdit}
          />
        </div>
      )}
    </li>
  );
}

/**
 * Display a connection UUID with a click-to-copy action. The full UUID is
 * shown inline so agents that ask "give me the connection_id" can be
 * answered by selecting/copying without opening any sub-view.
 */
function CopyableUuid({ value }: { value: string }) {
  const { t } = useTranslation('userfolder');
  const [copied, setCopied] = useState(false);
  async function copy() {
    try {
      await navigator.clipboard.writeText(value);
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2000);
    } catch {
      // Clipboard API can fail in non-secure contexts; user can still select manually.
    }
  }
  return (
    <button
      type="button"
      onClick={copy}
      title={t('ssh.copyUuid.title', { value })}
      className="font-mono hover:underline cursor-pointer text-slate-600 hover:text-accent-deep"
    >
      {copied ? t('ssh.copyUuid.copied') : value}
    </button>
  );
}

function ScopeBadge({ owner }: { owner: string | null }) {
  return owner === null ? (
    <Badge color="slate">global</Badge>
  ) : (
    <Badge color="blue">personal</Badge>
  );
}

function HostKeyBadge({ verified, pending }: { verified: boolean; pending: boolean }) {
  if (pending) return <Badge color="amber">host-key pending</Badge>;
  if (verified) return <Badge color="emerald">host-key verified</Badge>;
  return <Badge color="slate">host-key untested</Badge>;
}

function Badge({ color, children }: { color: 'slate' | 'blue' | 'emerald' | 'amber' | 'red'; children: React.ReactNode }) {
  const cls: Record<typeof color, string> = {
    slate: 'bg-slate-100 text-slate-600',
    blue: 'bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300',
    emerald: 'bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
    amber: 'bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300',
    red: 'bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300',
  };
  return (
    <span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${cls[color]}`}>
      {children}
    </span>
  );
}
