/**
 * useTerminal hook
 *
 * Manages a WebSocket connection to the /terminal namespace and a PTY terminal session.
 * Follows the same patterns as useVoiceInput and useWebSocket.
 *
 * Protocol (from terminal.gateway.ts):
 * 1. Connect with auth token in handshake
 * 2. Emit terminal:create → receive terminal:created { sessionId, name, cols, rows }
 * 3. Emit terminal:input { sessionId, data } to send keystrokes
 * 4. Receive terminal:output { sessionId, data } for stdout/stderr
 * 5. Emit terminal:resize { sessionId, cols, rows } on window resize
 * 6. Emit terminal:close { sessionId } to terminate the PTY
 * 7. Receive terminal:exit { sessionId, exitCode, signal } on PTY exit
 * 8. Receive terminal:error { message } on errors
 */

import { useEffect, useRef, useState, useCallback } from "react";
import type { Socket } from "socket.io-client";
import { io } from "socket.io-client";
import { API_BASE_URL } from "@/lib/config";

// ==========================================
// Types
// ==========================================

export interface CreateSessionOptions {
  name?: string;
  cols?: number;
  rows?: number;
  cwd?: string;
}

export interface TerminalSession {
  sessionId: string;
  name: string;
  cols: number;
  rows: number;
}

export interface TerminalExitEvent {
  sessionId: string;
  exitCode: number;
  signal?: string;
}

export interface UseTerminalOptions {
  /** Authentication token for WebSocket handshake */
  token: string;
  /** Callback fired when terminal output is received */
  onOutput?: (sessionId: string, data: string) => void;
  /** Callback fired when a terminal session exits */
  onExit?: (event: TerminalExitEvent) => void;
  /** Callback fired on terminal errors */
  onError?: (message: string) => void;
}

export interface UseTerminalReturn {
  /** Whether the WebSocket is connected */
  isConnected: boolean;
  /** The current terminal session ID, or null if no session is active */
  sessionId: string | null;
  /** Create a new PTY session */
  createSession: (options?: CreateSessionOptions) => void;
  /** Send input data to the terminal */
  sendInput: (data: string) => void;
  /** Resize the terminal PTY */
  resize: (cols: number, rows: number) => void;
  /** Close the current PTY session */
  closeSession: () => void;
  /** Connection error message, if any */
  connectionError: string | null;
}

// ==========================================
// Payload shapes matching terminal.dto.ts
// ==========================================

interface TerminalCreatedPayload {
  sessionId: string;
  name: string;
  cols: number;
  rows: number;
}

interface TerminalOutputPayload {
  sessionId: string;
  data: string;
}

interface TerminalExitPayload {
  sessionId: string;
  exitCode: number;
  signal?: string;
}

interface TerminalErrorPayload {
  message: string;
}

// ==========================================
// Security validation
// ==========================================

function validateWebSocketSecurity(url: string): void {
  const isProduction = process.env.NODE_ENV === "production";
  const isSecure = url.startsWith("https://") || url.startsWith("wss://");

  if (isProduction && !isSecure) {
    console.warn(
      "[Security Warning] Terminal WebSocket using insecure protocol (ws://). " +
        "Authentication tokens may be exposed. Use wss:// in production."
    );
  }
}

// ==========================================
// Hook
// ==========================================

/**
 * Hook for managing a real PTY terminal session over WebSocket.
 *
 * @param options - Configuration including auth token and event callbacks
 * @returns Terminal state and control functions
 */
export function useTerminal(options: UseTerminalOptions): UseTerminalReturn {
  const { token, onOutput, onExit, onError } = options;

  const [isConnected, setIsConnected] = useState(false);
  const [sessionId, setSessionId] = useState<string | null>(null);
  const [connectionError, setConnectionError] = useState<string | null>(null);

  const socketRef = useRef<Socket | null>(null);
  const sessionIdRef = useRef<string | null>(null);

  // Keep callbacks in refs to avoid stale closures without causing reconnects
  const onOutputRef = useRef(onOutput);
  const onExitRef = useRef(onExit);
  const onErrorRef = useRef(onError);

  useEffect(() => {
    onOutputRef.current = onOutput;
  }, [onOutput]);

  useEffect(() => {
    onExitRef.current = onExit;
  }, [onExit]);

  useEffect(() => {
    onErrorRef.current = onError;
  }, [onError]);

  // Connect to the /terminal namespace
  useEffect(() => {
    if (!token) {
      return;
    }

    const wsUrl = API_BASE_URL;
    validateWebSocketSecurity(wsUrl);

    setConnectionError(null);

    const socket = io(`${wsUrl}/terminal`, {
      auth: { token },
      transports: ["websocket", "polling"],
    });

    socketRef.current = socket;

    const handleConnect = (): void => {
      setIsConnected(true);
      setConnectionError(null);
    };

    const handleDisconnect = (): void => {
      setIsConnected(false);
      setSessionId(null);
      sessionIdRef.current = null;
    };

    const handleConnectError = (error: Error): void => {
      setConnectionError(error.message || "Terminal connection failed");
      setIsConnected(false);
    };

    const handleTerminalCreated = (payload: TerminalCreatedPayload): void => {
      setSessionId(payload.sessionId);
      sessionIdRef.current = payload.sessionId;
    };

    const handleTerminalOutput = (payload: TerminalOutputPayload): void => {
      onOutputRef.current?.(payload.sessionId, payload.data);
    };

    const handleTerminalExit = (payload: TerminalExitPayload): void => {
      onExitRef.current?.(payload);
      setSessionId(null);
      sessionIdRef.current = null;
    };

    const handleTerminalError = (payload: TerminalErrorPayload): void => {
      onErrorRef.current?.(payload.message);
    };

    socket.on("connect", handleConnect);
    socket.on("disconnect", handleDisconnect);
    socket.on("connect_error", handleConnectError);
    socket.on("terminal:created", handleTerminalCreated);
    socket.on("terminal:output", handleTerminalOutput);
    socket.on("terminal:exit", handleTerminalExit);
    socket.on("terminal:error", handleTerminalError);

    return (): void => {
      socket.off("connect", handleConnect);
      socket.off("disconnect", handleDisconnect);
      socket.off("connect_error", handleConnectError);
      socket.off("terminal:created", handleTerminalCreated);
      socket.off("terminal:output", handleTerminalOutput);
      socket.off("terminal:exit", handleTerminalExit);
      socket.off("terminal:error", handleTerminalError);

      // Close active session before disconnecting
      const currentSessionId = sessionIdRef.current;
      if (currentSessionId) {
        socket.emit("terminal:close", { sessionId: currentSessionId });
      }

      socket.disconnect();
      socketRef.current = null;
    };
  }, [token]);

  const createSession = useCallback((createOptions: CreateSessionOptions = {}): void => {
    const socket = socketRef.current;
    if (!socket?.connected) {
      return;
    }

    const payload: Record<string, unknown> = {};
    if (createOptions.name !== undefined) payload.name = createOptions.name;
    if (createOptions.cols !== undefined) payload.cols = createOptions.cols;
    if (createOptions.rows !== undefined) payload.rows = createOptions.rows;
    if (createOptions.cwd !== undefined) payload.cwd = createOptions.cwd;

    socket.emit("terminal:create", payload);
  }, []);

  const sendInput = useCallback((data: string): void => {
    const socket = socketRef.current;
    const currentSessionId = sessionIdRef.current;

    if (!socket?.connected || !currentSessionId) {
      return;
    }

    socket.emit("terminal:input", { sessionId: currentSessionId, data });
  }, []);

  const resize = useCallback((cols: number, rows: number): void => {
    const socket = socketRef.current;
    const currentSessionId = sessionIdRef.current;

    if (!socket?.connected || !currentSessionId) {
      return;
    }

    socket.emit("terminal:resize", { sessionId: currentSessionId, cols, rows });
  }, []);

  const closeSession = useCallback((): void => {
    const socket = socketRef.current;
    const currentSessionId = sessionIdRef.current;

    if (!socket?.connected || !currentSessionId) {
      return;
    }

    socket.emit("terminal:close", { sessionId: currentSessionId });
    setSessionId(null);
    sessionIdRef.current = null;
  }, []);

  return {
    isConnected,
    sessionId,
    createSession,
    sendInput,
    resize,
    closeSession,
    connectionError,
  };
}
