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

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

async function postJson<T>(url: string, body: Record<string, unknown>): Promise<T> {
  const res = await fetch(url, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(await parseError(res));
  return (await res.json()) as T;
}

async function patchJson<T>(url: string, body: Record<string, unknown>): Promise<T> {
  const res = await fetch(url, {
    method: 'PATCH',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(await parseError(res));
  return (await res.json()) as T;
}

async function deleteJson(url: string, body: Record<string, unknown>): Promise<void> {
  const res = await fetch(url, {
    method: 'DELETE',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(await parseError(res));
}

async function parseError(res: Response): Promise<string> {
  try {
    const j = await res.json();
    if (j?.error) return j.detail ? `${j.error}: ${typeof j.detail === 'string' ? j.detail : JSON.stringify(j.detail)}` : j.error;
  } catch {
    // ignore
  }
  return `HTTP ${res.status}`;
}

interface Props {
  showToast?: (msg: string, variant?: 'success' | 'error') => void;
  /** Notify parent when global connections changed (so grants section can re-fetch). */
  onChange?: () => void;
}

export function SshGlobalConnectionsForm({ showToast, onChange }: Props) {
  const qc = useQueryClient();
  const { data, isLoading, error } = useQuery({
    queryKey: ['ssh', 'admin', 'connections'],
    queryFn: fetchAdminConnections,
    staleTime: 15_000,
  });

  const [creating, setCreating] = useState(false);
  const [editingId, setEditingId] = useState<string | null>(null);
  const [reasonForOp, setReasonForOp] = useState<{ kind: 'disable' | 'enable' | 'delete' | 'forceUnlock'; conn: SshConnection } | 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);

  function invalidate() {
    qc.invalidateQueries({ queryKey: ['ssh', 'admin', 'connections'] });
    qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
    onChange?.();
  }

  const createMutation = useMutation({
    mutationFn: (body: Record<string, unknown>) =>
      postJson<{ connection: SshConnection; publicKey?: string | null }>('/api/ssh/admin/globals', body),
    onSuccess: (resp) => {
      invalidate();
      setCreating(false);
      showToast?.('グローバル接続を作成しました', 'success');
      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 res = await fetch(`/api/ssh/admin/connections/${encodeURIComponent(id)}`, { credentials: 'include' });
      if (!res.ok) throw new Error(await parseError(res));
      const data = (await res.json()) as { publicKey?: string | null };
      return { publicKey: data.publicKey ?? null, label };
    },
    onSuccess: ({ publicKey, label }) => {
      if (publicKey) {
        setPubKeyDialog({ publicKey, label, freshlyGenerated: false });
      } else {
        showToast?.('公開鍵の取得に失敗しました', 'error');
      }
    },
    onError: (e) => {
      showToast?.(e instanceof Error ? e.message : '公開鍵取得失敗', 'error');
    },
  });
  const patchMutation = useMutation({
    mutationFn: ({ id, body }: { id: string; body: Record<string, unknown> }) =>
      patchJson<{ connection: SshConnection }>(`/api/ssh/admin/globals/${encodeURIComponent(id)}`, body),
    onSuccess: () => {
      invalidate();
      setEditingId(null);
      showToast?.('グローバル接続を更新しました', 'success');
    },
  });
  const disableMutation = useMutation({
    mutationFn: ({ id, reason }: { id: string; reason: string }) =>
      patchJson(`/api/ssh/admin/connections/${encodeURIComponent(id)}/disable`, { reason }),
    onSuccess: () => { invalidate(); showToast?.('接続を無効化しました', 'success'); },
  });
  const enableMutation = useMutation({
    mutationFn: ({ id, reason }: { id: string; reason: string }) =>
      patchJson(`/api/ssh/admin/connections/${encodeURIComponent(id)}/enable`, { reason }),
    onSuccess: () => { invalidate(); showToast?.('接続を有効化しました', 'success'); },
  });
  const deleteMutation = useMutation({
    mutationFn: ({ id, reason }: { id: string; reason: string }) =>
      deleteJson(`/api/ssh/admin/globals/${encodeURIComponent(id)}`, { reason }),
    onSuccess: () => { invalidate(); showToast?.('接続を削除しました', 'success'); },
  });
  const forceUnlockMutation = useMutation({
    mutationFn: ({ id, reason }: { id: string; reason: string }) =>
      postJson(`/api/ssh/admin/connections/${encodeURIComponent(id)}/force-unlock`, { reason }),
    onSuccess: () => { invalidate(); showToast?.('アビューズロックを解除しました', 'success'); },
    onError: (e) => { showToast?.(e instanceof Error ? e.message : 'unlock 失敗', 'error'); },
  });
  const testMutation = useMutation({
    mutationFn: async (id: string): Promise<{ id: string; resp: TestResponse }> => {
      const res = await fetch(`/api/ssh/connections/${encodeURIComponent(id)}/test`, { method: 'POST', credentials: 'include' });
      if (!res.ok) throw new Error(await parseError(res));
      return { id, resp: (await res.json()) as TestResponse };
    },
    onSuccess: ({ id, resp }) => {
      invalidate();
      if (resp.verdict === 'pass') {
        showToast?.(`ホストキーは一致しています (${resp.fingerprint.slice(0, 20)}…)`, 'success');
      } else if (resp.verdict === 'first_observe' || resp.verdict === 'mismatch') {
        setTestResult({ id, test: resp, replaceMode: resp.verdict === 'mismatch' });
      } else if (resp.verdict === 'alg_not_allowed') {
        showToast?.('ホストキーのアルゴリズムが許可リストにありません', 'error');
      }
    },
    onError: (e) => { showToast?.(e instanceof Error ? e.message : 'テスト失敗', 'error'); },
  });

  async function handleVerify(connId: string, args: { fingerprint: string; token: string; reason?: string }) {
    const endpoint = args.reason ? 'replace-host-key' : 'verify-host-key';
    const res = await fetch(`/api/ssh/connections/${encodeURIComponent(connId)}/${endpoint}`, {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(args),
    });
    if (!res.ok) throw new Error(await parseError(res));
    invalidate();
    showToast?.('ホストキーを検証しました', 'success');
  }

  if (data?.sshDisabled) {
    return (
      <div className="text-xs text-slate-600 bg-surface border border-hairline rounded-md p-3 leading-relaxed">
        SSH サブシステムは無効です。<code className="font-mono">config.yaml</code> の{' '}
        <code className="font-mono">ssh.enabled: true</code> と <code className="font-mono">MCP_ENCRYPTION_KEY</code>{' '}
        を設定後にサーバーを再起動してください。
      </div>
    );
  }

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

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h3 className="text-sm font-semibold text-slate-900">グローバル接続 ({globals.length})</h3>
        <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}
        >
          + グローバル接続を追加
        </button>
      </div>

      {isLoading && <div className="text-xs text-slate-400">Loading…</div>}
      {error && <div className="text-xs text-red-500">{String(error)}</div>}

      {creating && (
        <section className="border border-accent/40 rounded-md bg-white p-4">
          <h4 className="text-xs font-semibold text-slate-700 mb-2">新規グローバル接続</h4>
          <SshConnectionForm
            existing={null}
            adminContext
            onSubmit={async (body) => { await createMutation.mutateAsync(body); }}
            onCancel={() => setCreating(false)}
          />
        </section>
      )}

      {globals.length === 0 && !creating && !isLoading && (
        <div className="text-xs text-slate-400 px-3 py-4">グローバル接続はまだありません。</div>
      )}

      <ul className="divide-y divide-hairline">
        {globals.map(c => (
          <li key={c.id} 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>
                  <Badge color="slate">global</Badge>
                  {c.hostKeyVerifiedAt ? (
                    <Badge color="emerald">host-key verified</Badge>
                  ) : c.hostKeyPending ? (
                    <Badge color="amber">host-key pending</Badge>
                  ) : (
                    <Badge color="slate">host-key untested</Badge>
                  )}
                  {c.disabledByAdmin && <Badge color="red">admin-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 mt-0.5">理由: {c.disabledByAdminReason}</div>
                )}
              </div>
              <div className="flex items-center gap-1 flex-shrink-0 flex-wrap justify-end max-w-[280px]">
                <button onClick={() => testMutation.mutate(c.id)} className={btnCls} disabled={testMutation.isPending && testMutation.variables === c.id}>
                  {testMutation.isPending && testMutation.variables === c.id ? 'テスト中…' : 'Test'}
                </button>
                <button
                  onClick={() => showPubKeyMutation.mutate({ id: c.id, label: c.label })}
                  disabled={showPubKeyMutation.isPending && showPubKeyMutation.variables?.id === c.id}
                  title="authorized_keys に貼る公開鍵を表示"
                  className={btnCls}
                >
                  {showPubKeyMutation.isPending && showPubKeyMutation.variables?.id === c.id ? '取得中…' : '公開鍵'}
                </button>
                <button onClick={() => { setEditingId(c.id); setCreating(false); }} className={btnCls}>
                  編集
                </button>
                <button onClick={() => setReasonForOp({ kind: 'forceUnlock', conn: c })} className={btnCls}>
                  force-unlock
                </button>
                {c.disabledByAdmin ? (
                  <button onClick={() => setReasonForOp({ kind: 'enable', conn: c })} className={btnCls}>有効化</button>
                ) : (
                  <button onClick={() => setReasonForOp({ kind: 'disable', conn: c })} className={btnCls}>無効化</button>
                )}
                <button onClick={() => setReasonForOp({ kind: 'delete', conn: c })} className={btnDangerCls}>削除</button>
              </div>
            </div>
            {editingId === c.id && (
              <div className="mt-3 ml-1 pl-3 border-l-2 border-accent/30">
                <SshConnectionForm
                  existing={c}
                  adminContext
                  onSubmit={async (body) => { await patchMutation.mutateAsync({ id: c.id, body }); }}
                  onCancel={() => setEditingId(null)}
                />
              </div>
            )}
          </li>
        ))}
      </ul>

      {reasonForOp && (
        <ReasonModal
          title={
            reasonForOp.kind === 'delete' ? `削除: ${reasonForOp.conn.label}` :
            reasonForOp.kind === 'disable' ? `無効化: ${reasonForOp.conn.label}` :
            reasonForOp.kind === 'enable' ? `有効化: ${reasonForOp.conn.label}` :
            `force-unlock: ${reasonForOp.conn.label}`
          }
          warning={reasonForOp.kind === 'delete'}
          onCancel={() => setReasonForOp(null)}
          onSubmit={async (reason) => {
            const op = reasonForOp;
            setReasonForOp(null);
            if (op.kind === 'delete') await deleteMutation.mutateAsync({ id: op.conn.id, reason });
            else if (op.kind === 'disable') await disableMutation.mutateAsync({ id: op.conn.id, reason });
            else if (op.kind === 'enable') await enableMutation.mutateAsync({ id: op.conn.id, reason });
            else if (op.kind === 'forceUnlock') await forceUnlockMutation.mutateAsync({ id: op.conn.id, reason });
          }}
        />
      )}

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

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

interface ReasonModalProps {
  title: string;
  warning?: boolean;
  onCancel: () => void;
  onSubmit: (reason: string) => Promise<void>;
}

function ReasonModal({ title, warning, onCancel, onSubmit }: ReasonModalProps) {
  const [reason, setReason] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleSubmit() {
    if (reason.trim().length < 8) return;
    setSubmitting(true);
    setError(null);
    try {
      await onSubmit(reason.trim());
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
      <div className="w-full max-w-md bg-white rounded-md shadow-lg border border-hairline overflow-hidden">
        <div className={`px-4 py-3 border-b border-hairline ${warning ? 'bg-red-50' : ''}`}>
          <h3 className={`text-sm font-semibold ${warning ? 'text-red-800' : 'text-slate-900'}`}>{title}</h3>
        </div>
        <div className="px-4 py-3 space-y-2">
          <label className="block text-2xs font-semibold text-slate-500 uppercase tracking-wide">
            Reason (≥ 8 chars)
          </label>
          <input
            type="text"
            value={reason}
            onChange={e => setReason(e.target.value)}
            className="w-full text-xs px-2 py-1.5 border border-hairline rounded"
            placeholder="監査ログに残す理由を記述"
            autoFocus
          />
          {error && <div className="text-xs text-red-600">{error}</div>}
        </div>
        <div className="px-4 py-3 border-t border-hairline flex items-center justify-end gap-2 bg-surface/50">
          <button onClick={onCancel} disabled={submitting} className={btnCls}>キャンセル</button>
          <button
            onClick={handleSubmit}
            disabled={submitting || reason.trim().length < 8}
            className={`px-3 h-7 text-xs font-semibold rounded-md disabled:opacity-50 ${warning ? 'bg-red-600 text-white hover:bg-red-700' : 'bg-accent text-accent-fg hover:bg-accent-deep'}`}
          >
            {submitting ? '送信中…' : '実行'}
          </button>
        </div>
      </div>
    </div>
  );
}

const btnCls = 'px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface disabled:opacity-50';
const btnDangerCls = 'px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 disabled:opacity-50';

/**
 * Click-to-copy connection UUID. Same UX as the user folder panel — agents
 * that ask "give me the connection_id" can be answered by clicking once.
 */
function CopyableUuid({ value }: { value: string }) {
  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={`クリックで UUID をコピー: ${value}`}
      className="font-mono hover:underline cursor-pointer text-slate-600 hover:text-accent-deep"
    >
      {copied ? '✓ コピーしました' : value}
    </button>
  );
}

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 text-blue-600',
    emerald: 'bg-emerald-50 text-emerald-700',
    amber: 'bg-amber-50 text-amber-700',
    red: 'bg-red-50 text-red-700',
  };
  return (
    <span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${cls[color]}`}>
      {children}
    </span>
  );
}
