import { useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import i18n from '../../../i18n';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useConsoleSession } from '../../../hooks/useConsoleSession';
import type { ConsoleStatus } from '../../../lib/ssh-console-types';
import type { SshConnection } from '../../../lib/ssh-types';
import { TerminalView, type TerminalViewHandle } from './console/TerminalView';
import { ConsoleHeader } from './console/ConsoleHeader';
import { MobileKeyboardBar } from './console/MobileKeyboardBar';
import { ScrollToBottomButton } from './console/ScrollToBottomButton';
import { useViewportNarrow } from '../../layout/TopBar';

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(`HTTP ${res.status}`);
  const data = (await res.json()) as { connections: SshConnection[] };
  return { list: data.connections ?? [], sshDisabled: false };
}

/** Map a structured `{ error }` code from the session endpoint to a user-facing message. */
function describeSessionError(code: string): { msg: string; hardStop: boolean } {
  switch (code) {
    case 'host_key_not_verified':
      return {
        msg: i18n.t('detail:console.errors.hostKeyNotVerified'),
        hardStop: false,
      };
    case 'no_grant':
      return {
        msg: i18n.t('detail:console.errors.noGrant'),
        hardStop: false,
      };
    case 'host_key_mismatch':
      return {
        msg: i18n.t('detail:console.errors.hostKeyMismatch'),
        hardStop: true,
      };
    case 'connection_disabled':
      return { msg: i18n.t('detail:console.errors.disabled'), hardStop: false };
    case 'abuse_locked':
      return { msg: i18n.t('detail:console.errors.abuseLocked'), hardStop: false };
    case 'connection_not_found':
      return { msg: i18n.t('detail:console.errors.notFound'), hardStop: false };
    default:
      return { msg: i18n.t('detail:console.errors.startFailed', { code }), hardStop: false };
  }
}

export function ConsoleTab({ taskId }: { taskId: number }) {
  const qc = useQueryClient();
  const { data: status } = useQuery<ConsoleStatus>({
    queryKey: ['console-status', taskId],
    queryFn: async () => {
      const r = await fetch(`/api/local/tasks/${taskId}/console/status`);
      return r.ok ? r.json() : { active: false };
    },
    refetchInterval: 5000,
  });
  const session = useConsoleSession(taskId);
  const terminalRef = useRef<TerminalViewHandle>(null);
  // 768px = Tailwind md breakpoint. Below this we consider the user to be on
  // a phone/tablet without a physical keyboard, so the on-screen keyboard bar
  // and scroll-to-bottom FAB become useful.
  const compactMode = useViewportNarrow(768);

  const showPicker = !status?.active;

  return (
    <div className="flex flex-col flex-1 min-h-0">
      <ConsoleHeader state={session.state} status={status ?? null} />
      <div className="flex-1 min-h-0 relative">
        {showPicker ? (
          <ConnectionPicker
            taskId={taskId}
            onStarted={() => {
              // The session now exists server-side; refresh status and force
              // an immediate WS attach instead of waiting for the 5s poll.
              qc.invalidateQueries({ queryKey: ['console-status', taskId] });
              session.reconnectNow();
            }}
          />
        ) : (
          <>
            <TerminalView ref={terminalRef} session={session} />
            {compactMode && <ScrollToBottomButton terminalRef={terminalRef} />}
          </>
        )}
      </div>
      {compactMode && !showPicker && <MobileKeyboardBar session={session} />}
    </div>
  );
}

function ConnectionPicker({
  taskId,
  onStarted,
}: {
  taskId: number;
  onStarted: () => void;
}) {
  const { t } = useTranslation('detail');
  const { data, isLoading, error } = useQuery({
    queryKey: ['ssh', 'connections'],
    queryFn: fetchConnections,
    staleTime: 15_000,
  });

  const connections = useMemo(
    () => (data?.list ?? []).filter((c) => c.enabled && !c.disabledByAdmin),
    [data],
  );

  const [selectedId, setSelectedId] = useState<string>('');
  const [submitting, setSubmitting] = useState(false);
  const [errMsg, setErrMsg] = useState<{ msg: string; hardStop: boolean } | null>(null);
  // Set when the server reports an existing session on a different connection;
  // lets the user re-POST with force_replace to take over the session.
  const [replaceCandidate, setReplaceCandidate] = useState<string | null>(null);

  // Default the select to the first connection once loaded.
  const effectiveId = selectedId || connections[0]?.id || '';

  async function start(forceReplace: boolean) {
    if (!effectiveId) return;
    setSubmitting(true);
    setErrMsg(null);
    setReplaceCandidate(null);
    try {
      const res = await fetch(`/api/local/tasks/${taskId}/console/session`, {
        method: 'POST',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          connection_id: effectiveId,
          ...(forceReplace ? { force_replace: true } : {}),
        }),
      });
      if (res.ok) {
        onStarted();
        return;
      }
      let code = `HTTP ${res.status}`;
      try {
        const j = (await res.json()) as { error?: string };
        if (j?.error) code = j.error;
      } catch {
        // non-JSON body; keep HTTP status as the code
      }
      if (code === 'connection_change_requires_force') {
        setReplaceCandidate(effectiveId);
        setErrMsg({
          msg: i18n.t('detail:console.errors.sessionExists'),
          hardStop: false,
        });
        return;
      }
      setErrMsg(describeSessionError(code));
    } catch (e) {
      setErrMsg({ msg: e instanceof Error ? e.message : String(e), hardStop: false });
    } finally {
      setSubmitting(false);
    }
  }

  if (data?.sshDisabled) {
    return (
      <div className="absolute inset-0 flex items-center justify-center p-6 bg-[#0b1020]">
        <div className="max-w-md text-xs text-slate-300 bg-surface/10 border border-hairline rounded-md p-4 leading-relaxed">
          {t('console.sshDisabled')}
        </div>
      </div>
    );
  }

  return (
    <div className="absolute inset-0 flex items-center justify-center p-6 bg-[#0b1020]">
      <div className="w-full max-w-md space-y-3">
        <div>
          <h3 className="text-sm font-semibold text-slate-100">{t('console.startTitle')}</h3>
          <p className="text-2xs text-slate-400 mt-0.5">
            {t('console.startDesc')}
          </p>
        </div>

        {isLoading && <div className="text-xs text-slate-400">Loading…</div>}
        {error && <div className="text-xs text-red-400">{t('console.loadFailed')}: {String(error)}</div>}

        {!isLoading && connections.length === 0 ? (
          <div className="text-xs text-slate-300 bg-surface/10 border border-hairline rounded-md p-3 leading-relaxed">
            {t('console.noConnections')}
          </div>
        ) : (
          <>
            <select
              value={effectiveId}
              onChange={(e) => { setSelectedId(e.target.value); setErrMsg(null); setReplaceCandidate(null); }}
              disabled={submitting}
              className="w-full text-xs px-2 py-1.5 bg-surface border border-hairline rounded text-slate-100 disabled:opacity-50"
            >
              {connections.map((c) => (
                <option key={c.id} value={c.id}>
                  {c.label} — {c.username}@{c.host}:{c.port}
                </option>
              ))}
            </select>

            <button
              type="button"
              onClick={() => start(false)}
              disabled={submitting || !effectiveId}
              className="w-full px-3 h-8 text-xs font-semibold bg-accent text-accent-fg rounded-md hover:bg-accent-deep disabled:opacity-50"
            >
              {submitting ? t('console.starting') : t('console.startSession')}
            </button>

            {replaceCandidate && (
              <button
                type="button"
                onClick={() => start(true)}
                disabled={submitting}
                className="w-full px-3 h-8 text-xs font-semibold border border-amber-400/50 text-amber-300 rounded-md hover:bg-amber-500/15 disabled:opacity-50"
              >
                {t('console.replaceStart')}
              </button>
            )}
          </>
        )}

        {errMsg && (
          <div
            className={
              errMsg.hardStop
                ? 'text-xs text-red-200 bg-red-500/20 border border-red-500/50 rounded-md p-3 leading-relaxed'
                : 'text-xs text-amber-200 bg-amber-500/10 border border-amber-500/30 rounded-md p-3 leading-relaxed'
            }
          >
            {errMsg.msg}
          </div>
        )}
      </div>
    </div>
  );
}
