import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { SshConnection, SshGrant, SshGrantSubjectType } from '../../lib/ssh-types';

// NOTE: queryKey `['ssh', 'admin', 'connections']` is shared with SshGlobalConnectionsForm.
// Both fetchers must return the same shape, otherwise the cache last-writer wins and
// the other component reads an object where it expects an array — `.filter is not
// a function` crash. Keep the `{list, sshDisabled}` shape in lockstep.
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 fetchAdminGrants(): Promise<{ list: SshGrant[]; sshDisabled: boolean }> {
  const res = await fetch('/api/ssh/admin/grants?limit=1000', { 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 { grants: SshGrant[] };
  return { list: data.grants ?? [], sshDisabled: false };
}

async function fetchPieces(): Promise<string[]> {
  const res = await fetch('/api/pieces', { credentials: 'include' });
  if (!res.ok) return [];
  const data = (await res.json()) as { pieces: Array<{ name: string }> };
  return (data.pieces ?? []).map(p => p.name).sort();
}

async function postJson(url: string, body: Record<string, unknown>): Promise<unknown> {
  const res = await fetch(url, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    let detail = '';
    try {
      const j = await res.json();
      detail = j?.error ? (j.detail ? `${j.error}: ${typeof j.detail === 'string' ? j.detail : JSON.stringify(j.detail)}` : j.error) : '';
    } catch { /* ignore */ }
    throw new Error(detail || `HTTP ${res.status}`);
  }
  return await res.json();
}

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(`HTTP ${res.status}`);
}

interface Props {
  showToast?: (msg: string, variant?: 'success' | 'error') => void;
}

/**
 * Admin grant management. Lists all grants and lets the admin create new ones
 * (per-piece or applies-to-all) and delete existing ones (reason required).
 *
 * UI groups grants by global connection so it's easy to see who can use what.
 */
export function SshGrantsForm({ showToast }: Props) {
  const qc = useQueryClient();
  const connQuery = useQuery({ queryKey: ['ssh', 'admin', 'connections'], queryFn: fetchAdminConnections, staleTime: 15_000 });
  const grantsQuery = useQuery({ queryKey: ['ssh', 'admin', 'grants'], queryFn: fetchAdminGrants, staleTime: 15_000 });
  const piecesQuery = useQuery({ queryKey: ['pieces', 'names'], queryFn: fetchPieces, staleTime: 60_000 });

  const [showCreate, setShowCreate] = useState(false);
  const [reasonForDelete, setReasonForDelete] = useState<SshGrant | null>(null);

  const createMutation = useMutation({
    mutationFn: (body: Record<string, unknown>) => postJson('/api/ssh/admin/grants', body),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['ssh', 'admin', 'grants'] });
      showToast?.('Grant を作成しました', 'success');
      setShowCreate(false);
    },
  });
  const deleteMutation = useMutation({
    mutationFn: ({ id, reason }: { id: string; reason: string }) =>
      deleteJson(`/api/ssh/admin/grants/${encodeURIComponent(id)}`, { reason }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['ssh', 'admin', 'grants'] });
      showToast?.('Grant を削除しました', 'success');
    },
  });

  const sshDisabled = connQuery.data?.sshDisabled === true || grantsQuery.data?.sshDisabled === true;
  const globalConns = (connQuery.data?.list ?? []).filter(c => c.ownerId === null);
  const grantsByConn = new Map<string, SshGrant[]>();
  for (const g of grantsQuery.data?.list ?? []) {
    if (!grantsByConn.has(g.connectionId)) grantsByConn.set(g.connectionId, []);
    grantsByConn.get(g.connectionId)!.push(g);
  }

  if (sshDisabled) {
    return (
      <div className="space-y-4">
        <h3 className="text-sm font-semibold text-slate-900">アクセス権 (grants)</h3>
        <div className="border border-amber-200 rounded-md bg-amber-50 p-4 text-xs text-amber-900">
          <div className="font-semibold mb-1">SSH サブシステムが無効です</div>
          <div>
            <code className="font-mono">config.yaml</code> で <code className="font-mono">ssh.enabled: true</code> を設定し、
            環境変数 <code className="font-mono">MCP_ENCRYPTION_KEY</code> (64 hex chars) を export してから
            サーバーを再起動してください。詳細は <code className="font-mono">docs/ssh.md</code> を参照。
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h3 className="text-sm font-semibold text-slate-900">アクセス権 (grants)</h3>
        <button
          type="button"
          onClick={() => setShowCreate(true)}
          disabled={globalConns.length === 0}
          className="px-3 h-7 text-xs font-semibold bg-accent text-accent-fg rounded-md hover:bg-accent-deep disabled:opacity-50"
        >
          + Grant を発行
        </button>
      </div>
      {globalConns.length === 0 && (
        <div className="text-xs text-slate-400 px-3 py-2">
          まずグローバル接続を登録してから grant を発行できます。
        </div>
      )}

      {showCreate && (
        <CreateGrantForm
          connections={globalConns}
          pieces={piecesQuery.data ?? []}
          onCancel={() => setShowCreate(false)}
          onSubmit={async (body) => { await createMutation.mutateAsync(body); }}
        />
      )}

      <div className="space-y-3">
        {globalConns.map(c => {
          const grants = grantsByConn.get(c.id) ?? [];
          return (
            <section key={c.id} className="border border-hairline rounded-md bg-white">
              <header className="px-3 py-2 border-b border-hairline bg-surface/40 flex items-center justify-between">
                <div className="min-w-0 flex-1">
                  <div className="text-xs font-semibold text-slate-900 truncate">{c.label}</div>
                  <div className="text-2xs text-slate-500 font-mono truncate">
                    {c.username}@{c.host}:{c.port}
                  </div>
                </div>
                <span className="text-2xs text-slate-500 font-mono">{grants.length} grants</span>
              </header>
              {grants.length === 0 ? (
                <div className="px-3 py-3 text-2xs text-slate-400">grant がありません — ユーザーは利用できません。</div>
              ) : (
                <ul className="divide-y divide-hairline">
                  {grants.map(g => (
                    <li key={g.id} className="px-3 py-2 flex items-start justify-between gap-2">
                      <div className="min-w-0 flex-1">
                        <div className="text-2xs">
                          <span className="font-mono font-semibold">{g.subjectType}:{g.subjectId}</span>
                          {g.appliesToAllPieces ? (
                            <span className="ml-2 inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-50 text-amber-700">
                              all pieces
                            </span>
                          ) : (
                            <span className="ml-2 text-slate-700">piece: <span className="font-mono">{g.pieceName}</span></span>
                          )}
                        </div>
                        <div className="text-2xs text-slate-500 mt-0.5">
                          理由: {g.reason}
                          {g.expiresAt && <> · 失効: <span className="font-mono">{g.expiresAt}</span></>}
                        </div>
                      </div>
                      <button
                        onClick={() => setReasonForDelete(g)}
                        className="px-2 h-6 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50"
                      >
                        取消
                      </button>
                    </li>
                  ))}
                </ul>
              )}
            </section>
          );
        })}
      </div>

      {reasonForDelete && (
        <ReasonModal
          title={`Grant を取り消す`}
          warning
          onCancel={() => setReasonForDelete(null)}
          onSubmit={async (reason) => {
            const g = reasonForDelete;
            setReasonForDelete(null);
            await deleteMutation.mutateAsync({ id: g.id, reason });
          }}
        />
      )}
    </div>
  );
}

interface CreateGrantFormProps {
  connections: SshConnection[];
  pieces: string[];
  onSubmit: (body: Record<string, unknown>) => Promise<void>;
  onCancel: () => void;
}

function CreateGrantForm({ connections, pieces, onSubmit, onCancel }: CreateGrantFormProps) {
  const [connectionId, setConnectionId] = useState(connections[0]?.id ?? '');
  const [subjectType, setSubjectType] = useState<SshGrantSubjectType>('user');
  const [subjectId, setSubjectId] = useState('');
  const [appliesToAll, setAppliesToAll] = useState(false);
  const [pieceName, setPieceName] = useState('');
  const [expiresAt, setExpiresAt] = useState('');
  const [reason, setReason] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const valid =
    connectionId.length > 0 &&
    subjectId.trim().length > 0 &&
    (appliesToAll || pieceName.trim().length > 0) &&
    reason.trim().length >= 8;

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!valid) return;
    setSubmitting(true);
    setError(null);
    const body: Record<string, unknown> = {
      connectionId,
      subjectType,
      subjectId: subjectId.trim(),
      appliesToAllPieces: appliesToAll,
      reason: reason.trim(),
    };
    if (!appliesToAll) body.pieceName = pieceName.trim();
    if (expiresAt.trim().length > 0) body.expiresAt = expiresAt.trim();
    try {
      await onSubmit(body);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setSubmitting(false);
    }
  }

  const inputCls = 'w-full text-xs px-2 py-1.5 border border-hairline rounded';

  return (
    <form onSubmit={handleSubmit} className="border border-accent/40 rounded-md bg-white p-4 space-y-3">
      <h4 className="text-xs font-semibold text-slate-700">Grant を発行</h4>
      <div className="grid grid-cols-2 gap-3">
        <label className="block">
          <div className="text-2xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Global connection</div>
          <select value={connectionId} onChange={e => setConnectionId(e.target.value)} className={inputCls}>
            {connections.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
          </select>
        </label>
        <label className="block">
          <div className="text-2xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Subject</div>
          <div className="flex gap-1">
            <select
              value={subjectType}
              onChange={e => setSubjectType(e.target.value as SshGrantSubjectType)}
              className="shrink-0 w-20 text-xs px-2 py-1.5 border border-hairline rounded"
            >
              <option value="user">user</option>
              <option value="org">org</option>
            </select>
            <input
              type="text"
              value={subjectId}
              onChange={e => setSubjectId(e.target.value)}
              placeholder={subjectType === 'user' ? 'gitea ユーザー ID' : 'org ID'}
              className="flex-1 min-w-0 text-xs px-2 py-1.5 border border-hairline rounded font-mono"
              required
            />
          </div>
        </label>
      </div>
      <div>
        <label className="flex items-start gap-2 text-xs cursor-pointer mb-2">
          <input
            type="checkbox"
            checked={appliesToAll}
            onChange={e => setAppliesToAll(e.target.checked)}
            className="mt-0.5"
          />
          <span>
            <span className="font-semibold">すべてのピースで利用可能 (applies_to_all_pieces)</span>
            <span className="block text-2xs text-amber-700">
              ⚠️ この grant は任意の piece からこの接続を使えるようにします。本当に必要なときのみ。
            </span>
          </span>
        </label>
        {!appliesToAll && (
          <label className="block">
            <div className="text-2xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Piece name</div>
            <input
              type="text"
              value={pieceName}
              onChange={e => setPieceName(e.target.value)}
              placeholder="piece 名 (例: db-maintenance)"
              className={inputCls + ' font-mono'}
              list="ssh-grant-piece-list"
              required
            />
            <datalist id="ssh-grant-piece-list">
              {pieces.map(p => <option key={p} value={p} />)}
            </datalist>
          </label>
        )}
      </div>
      <div className="grid grid-cols-2 gap-3">
        <label className="block">
          <div className="text-2xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Expires at (任意, ISO8601)</div>
          <input
            type="text"
            value={expiresAt}
            onChange={e => setExpiresAt(e.target.value)}
            placeholder="2026-12-31T00:00:00Z"
            className={inputCls + ' font-mono'}
          />
        </label>
        <label className="block">
          <div className="text-2xs font-semibold text-slate-500 uppercase tracking-wide mb-1">Reason (≥ 8 chars)</div>
          <input
            type="text"
            value={reason}
            onChange={e => setReason(e.target.value)}
            placeholder="運用上の理由"
            className={inputCls}
            required
          />
        </label>
      </div>
      {error && <div className="text-xs text-red-600">{error}</div>}
      <div className="flex items-center justify-end gap-2 pt-2 border-t border-hairline">
        <button type="button" onClick={onCancel} disabled={submitting} className="px-3 h-7 text-xs text-slate-700 border border-hairline bg-white rounded-md hover:bg-surface disabled:opacity-50">
          キャンセル
        </button>
        <button type="submit" disabled={!valid || submitting} className="px-3 h-7 text-xs font-semibold bg-accent text-accent-fg rounded-md hover:bg-accent-deep disabled:opacity-50">
          {submitting ? '発行中…' : '発行'}
        </button>
      </div>
    </form>
  );
}

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="px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface disabled:opacity-50">キャンセル</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>
  );
}
