import { getErrorMessage } from "../../../lib/error-message.js";
import React from "react";
import { useXTerm } from "react-xtermjs";
import { FitAddon } from "@xterm/addon-fit";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { RobustClipboardProvider } from "@/lib/clipboard-provider";
import { copyToClipboard, readFromClipboard } from "@/lib/clipboard";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { Button } from "@/components/button.tsx";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/select.tsx";
import { Card, CardContent } from "@/components/card.tsx";
import { getBasePath } from "@/lib/base-path";
import {
  resolveConnectionOrigin,
  buildOriginWsUrl,
} from "@/lib/connection-origin.ts";
import { Terminal as TerminalIcon, Power, PowerOff } from "lucide-react";
import { toast } from "sonner";
import type { SSHHost } from "@/types";
import { isElectron } from "@/main-axios.ts";
import { useTranslation } from "react-i18next";
import { resolveTermixThemeColors } from "@/features/terminal/terminal-theme";
import { DEFAULT_TERMINAL_CONFIG, TERMINAL_FONTS } from "@/lib/terminal-themes";
import { ensureTerminalFontsLoaded } from "@/features/terminal/terminal-global-styles";
import { useTheme } from "@/components/theme-provider";
import { ConnectionScreen } from "@/components/connection/ConnectionScreen.tsx";
import {
  ConnectionLogProvider,
  useConnectionLog,
} from "@/ssh/connection-log/ConnectionLogContext.tsx";
import { useConnectionRetry } from "@/lib/useConnectionRetry.ts";

interface ConsoleTerminalProps {
  containerId: string;
  containerName: string;
  containerState: string;
  hostConfig: SSHHost;
}

export function ConsoleTerminal(
  props: ConsoleTerminalProps,
): React.ReactElement {
  return (
    <ConnectionLogProvider>
      <ConsoleTerminalInner {...props} />
    </ConnectionLogProvider>
  );
}

function ConsoleTerminalInner({
  containerId,
  containerName,
  containerState,
  hostConfig,
}: ConsoleTerminalProps): React.ReactElement {
  const { t } = useTranslation();
  const { theme: appTheme } = useTheme();
  const { instance: terminal, ref: xtermRef } = useXTerm();
  const { addLog, clearLogs } = useConnectionLog();

  const terminalConfig = React.useMemo(
    () => ({ ...DEFAULT_TERMINAL_CONFIG, ...hostConfig.terminalConfig }),
    [hostConfig.terminalConfig],
  );

  const themeColors = React.useMemo(() => {
    const activeTheme = terminalConfig.theme;
    return resolveTermixThemeColors(activeTheme, appTheme);
  }, [terminalConfig.theme, appTheme]);

  const [isConnected, setIsConnected] = React.useState(false);
  const [isConnecting, setIsConnecting] = React.useState(false);
  const [selectedShell, setSelectedShell] = React.useState<string>("bash");
  const wsRef = React.useRef<WebSocket | null>(null);
  const fitAddonRef = React.useRef<FitAddon | null>(null);
  const pingIntervalRef = React.useRef<NodeJS.Timeout | null>(null);

  React.useEffect(() => {
    if (!terminal) return;

    const fitAddon = new FitAddon();
    const clipboardProvider = new RobustClipboardProvider();
    const clipboardAddon = new ClipboardAddon(undefined, clipboardProvider);
    const webLinksAddon = new WebLinksAddon();

    fitAddonRef.current = fitAddon;

    terminal.loadAddon(fitAddon);
    terminal.loadAddon(clipboardAddon);
    terminal.loadAddon(webLinksAddon);

    const fontConfig = TERMINAL_FONTS.find(
      (f) => f.value === terminalConfig.fontFamily,
    );
    const fontFamily = fontConfig?.fallback ?? TERMINAL_FONTS[0].fallback;
    ensureTerminalFontsLoaded(fontConfig?.value ?? TERMINAL_FONTS[0].value);

    terminal.options.cursorBlink = terminalConfig.cursorBlink;
    terminal.options.cursorStyle = terminalConfig.cursorStyle;
    terminal.options.fontSize = terminalConfig.fontSize;
    terminal.options.fontFamily = fontFamily;
    terminal.options.scrollback = terminalConfig.scrollback;
    terminal.options.letterSpacing = terminalConfig.letterSpacing;
    terminal.options.lineHeight = terminalConfig.lineHeight;

    const readTextFromClipboard = async (): Promise<string> => {
      return readFromClipboard();
    };

    const writeTextToClipboard = async (text: string): Promise<void> => {
      await copyToClipboard(text);
    };

    terminal.attachCustomKeyEventHandler((e: KeyboardEvent): boolean => {
      if (e.type !== "keydown") return true;

      if (
        ((e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) ||
          (e.metaKey && !e.shiftKey && !e.ctrlKey && !e.altKey)) &&
        e.key.toLowerCase() === "v"
      ) {
        e.preventDefault();
        e.stopPropagation();
        readTextFromClipboard()
          .then((text) => {
            if (text) terminal.paste(text);
          })
          .catch(() => {
            toast.error(t("terminal.clipboardReadFailed"));
          });
        return false;
      }

      if (
        e.ctrlKey &&
        !e.shiftKey &&
        !e.altKey &&
        !e.metaKey &&
        e.key.toLowerCase() === "c" &&
        terminal.hasSelection()
      ) {
        e.preventDefault();
        e.stopPropagation();
        const selection = terminal.getSelection();
        if (selection) {
          writeTextToClipboard(selection).catch(() => {
            toast.error(t("terminal.clipboardWriteFailed"));
          });
          terminal.clearSelection();
        }
        return false;
      }

      if (
        e.ctrlKey &&
        !e.shiftKey &&
        !e.altKey &&
        !e.metaKey &&
        e.key === "Insert" &&
        terminal.hasSelection()
      ) {
        e.preventDefault();
        e.stopPropagation();
        const selection = terminal.getSelection();
        if (selection) {
          writeTextToClipboard(selection).catch(() => {
            toast.error(t("terminal.clipboardWriteFailed"));
          });
        }
        return false;
      }

      if (
        e.shiftKey &&
        !e.ctrlKey &&
        !e.altKey &&
        !e.metaKey &&
        e.key === "Insert"
      ) {
        e.preventDefault();
        e.stopPropagation();
        readTextFromClipboard()
          .then((text) => {
            if (text) terminal.paste(text);
          })
          .catch(() => {
            toast.error(t("terminal.clipboardReadFailed"));
          });
        return false;
      }

      return true;
    });

    terminal.options.theme = {
      background: themeColors.background,
      foreground: themeColors.foreground,
      cursor: themeColors.cursor,
      cursorAccent: themeColors.cursorAccent,
      selectionBackground: themeColors.selectionBackground,
      selectionForeground: themeColors.selectionForeground,
      black: themeColors.black,
      red: themeColors.red,
      green: themeColors.green,
      yellow: themeColors.yellow,
      blue: themeColors.blue,
      magenta: themeColors.magenta,
      cyan: themeColors.cyan,
      white: themeColors.white,
      brightBlack: themeColors.brightBlack,
      brightRed: themeColors.brightRed,
      brightGreen: themeColors.brightGreen,
      brightYellow: themeColors.brightYellow,
      brightBlue: themeColors.brightBlue,
      brightMagenta: themeColors.brightMagenta,
      brightCyan: themeColors.brightCyan,
      brightWhite: themeColors.brightWhite,
    };

    setTimeout(() => {
      fitAddon.fit();
    }, 100);

    const resizeHandler = () => {
      if (fitAddonRef.current) {
        fitAddonRef.current.fit();

        if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
          const { rows, cols } = terminal;
          wsRef.current.send(
            JSON.stringify({
              type: "resize",
              data: { rows, cols },
            }),
          );
        }
      }
    };

    window.addEventListener("resize", resizeHandler);

    return () => {
      window.removeEventListener("resize", resizeHandler);
      clipboardProvider.dispose();

      if (wsRef.current) {
        try {
          wsRef.current.send(JSON.stringify({ type: "disconnect" }));
        } catch {
          // Best-effort disconnect during cleanup.
        }
        wsRef.current.close();
        wsRef.current = null;
      }

      terminal.dispose();
    };
  }, [terminal, t, terminalConfig, themeColors]);

  const disconnect = React.useCallback(() => {
    if (wsRef.current) {
      try {
        wsRef.current.send(JSON.stringify({ type: "disconnect" }));
      } catch {
        // Best-effort disconnect.
      }
      wsRef.current.close();
      wsRef.current = null;
    }
    setIsConnected(false);
    retryRef.current.reset();
    clearLogs();
    if (terminal) {
      try {
        terminal.clear();
      } catch {
        // Terminal clear can fail after disposal.
      }
    }
  }, [terminal, clearLogs]);

  const connect = React.useCallback(async () => {
    if (!terminal || containerState !== "running") {
      toast.error(t("docker.containerMustBeRunning"));
      return;
    }

    setIsConnecting(true);
    addLog({
      type: "info",
      stage: "docker_connecting",
      message: t("docker.connectingTo", { containerName }),
    });

    try {
      if (fitAddonRef.current) {
        fitAddonRef.current.fit();
      }

      const isElectronApp = isElectron();

      const isDev =
        !isElectronApp &&
        process.env.NODE_ENV === "development" &&
        (window.location.port === "3000" ||
          window.location.port === "5173" ||
          window.location.port === "");

      let baseWsUrl: string;
      if (isDev) {
        baseWsUrl = `${window.location.protocol === "https:" ? "wss" : "ws"}://localhost:30009`;
      } else if (isElectronApp) {
        const origin = await resolveConnectionOrigin({
          connectionType: "ssh",
          connectionOrigin: hostConfig.connectionOrigin,
        });
        const resolvedUrl = await buildOriginWsUrl({
          origin,
          localPort: 30009,
          localPath: "/docker/console/",
          remotePath: "/docker/console/",
        });
        if (!resolvedUrl) {
          setIsConnecting(false);
          toast.error(t("errors.remoteServerRequired"));
          return;
        }
        baseWsUrl = resolvedUrl;
      } else {
        baseWsUrl = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}${getBasePath()}/docker/console/`;
      }

      const ws = new WebSocket(baseWsUrl);

      ws.onopen = () => {
        const cols = terminal.cols || 80;
        const rows = terminal.rows || 24;

        ws.send(
          JSON.stringify({
            type: "connect",
            data: {
              hostConfig,
              containerId,
              shell: selectedShell,
              cols,
              rows,
            },
          }),
        );
      };

      ws.onmessage = (event) => {
        try {
          const msg = JSON.parse(event.data);

          switch (msg.type) {
            case "output":
              terminal.write(msg.data);
              break;

            case "connected":
              setIsConnected(true);
              setIsConnecting(false);
              retryRef.current.markConnected();

              if (msg.data?.shellChanged) {
                toast.warning(
                  `Shell "${msg.data.requestedShell}" not available. Using "${msg.data.shell}" instead.`,
                );
              } else {
                toast.success(t("docker.connectedTo", { containerName }));
              }

              setTimeout(() => {
                if (fitAddonRef.current) {
                  fitAddonRef.current.fit();
                }

                if (ws.readyState === WebSocket.OPEN) {
                  ws.send(
                    JSON.stringify({
                      type: "resize",
                      data: { rows: terminal.rows, cols: terminal.cols },
                    }),
                  );
                }
              }, 100);
              break;

            case "disconnected":
              setIsConnected(false);
              setIsConnecting(false);
              retryRef.current.reset();
              terminal.write(
                `\r\n\x1b[1;33m${msg.message || t("docker.disconnected")}\x1b[0m\r\n`,
              );
              if (wsRef.current) {
                wsRef.current.close();
                wsRef.current = null;
              }
              break;

            case "error":
              setIsConnecting(false);
              toast.error(msg.message || t("docker.consoleError"));
              terminal.write(
                `\r\n\x1b[1;31m${t("docker.errorMessage", { message: msg.message })}\x1b[0m\r\n`,
              );
              addLog({
                type: "error",
                stage: "error",
                message: msg.message || t("docker.consoleError"),
              });
              retryRef.current.markFailed();
              break;
          }
        } catch (error) {
          console.error("Failed to parse WebSocket message:", error);
        }
      };

      ws.onerror = (error) => {
        console.error("WebSocket error:", error);
        setIsConnecting(false);
        setIsConnected(false);
        toast.error(t("docker.failedToConnect"));
        addLog({
          type: "error",
          stage: "error",
          message: t("docker.failedToConnect"),
        });
        retryRef.current.markFailed();
      };

      if (pingIntervalRef.current) {
        clearInterval(pingIntervalRef.current);
      }
      pingIntervalRef.current = setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ type: "ping" }));
        }
      }, 30000);

      ws.onclose = () => {
        if (pingIntervalRef.current) {
          clearInterval(pingIntervalRef.current);
          pingIntervalRef.current = null;
        }
        setIsConnected(false);
        setIsConnecting(false);
        if (wsRef.current === ws) {
          wsRef.current = null;
        }
      };

      wsRef.current = ws;

      terminal.onData((data) => {
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(
            JSON.stringify({
              type: "input",
              data,
            }),
          );
        }
      });
    } catch (error) {
      setIsConnecting(false);
      const message = `Failed to connect: ${getErrorMessage(error)}`;
      toast.error(message);
      addLog({ type: "error", stage: "error", message });
      retryRef.current.markFailed();
    }
  }, [
    terminal,
    containerState,
    hostConfig,
    containerId,
    selectedShell,
    containerName,
    t,
    addLog,
  ]);

  const retry = useConnectionRetry({
    connect,
    autoStart: false,
  });
  const retryRef = React.useRef(retry);
  retryRef.current = retry;

  React.useEffect(() => {
    return () => {
      if (pingIntervalRef.current) {
        clearInterval(pingIntervalRef.current);
        pingIntervalRef.current = null;
      }
      if (wsRef.current) {
        try {
          wsRef.current.send(JSON.stringify({ type: "disconnect" }));
        } catch {
          // Best-effort disconnect during cleanup.
        }
        wsRef.current.close();
        wsRef.current = null;
      }
      setIsConnected(false);
    };
  }, []);

  if (containerState !== "running") {
    return (
      <div className="flex items-center justify-center h-full">
        <div className="text-center space-y-2">
          <TerminalIcon className="h-12 w-12 text-muted-foreground/50 mx-auto" />
          <p className="text-muted-foreground text-lg">
            {t("docker.containerNotRunning")}
          </p>
          <p className="text-muted-foreground text-sm">
            {t("docker.startContainerToAccess")}
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="flex flex-col h-full gap-3">
      <Card className="py-3">
        <CardContent className="px-3">
          <div className="flex flex-col sm:flex-row gap-2 items-center sm:items-center">
            <div className="flex items-center gap-2 flex-1">
              <TerminalIcon className="h-5 w-5" />
              <span className="text-base font-medium">
                {t("docker.console")}
              </span>
            </div>
            <Select
              value={selectedShell}
              onValueChange={setSelectedShell}
              disabled={isConnected}
            >
              <SelectTrigger className="w-[120px]">
                <SelectValue placeholder={t("docker.selectShell")} />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="bash">{t("docker.bash")}</SelectItem>
                <SelectItem value="sh">{t("docker.sh")}</SelectItem>
                <SelectItem value="ash">{t("docker.ash")}</SelectItem>
              </SelectContent>
            </Select>
            <div className="flex gap-2 sm:gap-2">
              {!isConnected ? (
                <Button
                  onClick={connect}
                  disabled={isConnecting}
                  className="min-w-[120px]"
                >
                  {isConnecting ? (
                    <>
                      <div className="h-4 w-4 mr-2 animate-spin rounded-full border-2 border-gray-400 border-t-transparent" />
                      {t("docker.connecting")}
                    </>
                  ) : (
                    <>
                      <Power className="h-4 w-4 mr-2" />
                      {t("docker.connect")}
                    </>
                  )}
                </Button>
              ) : (
                <Button
                  onClick={disconnect}
                  variant="destructive"
                  className="min-w-[120px]"
                >
                  <PowerOff className="h-4 w-4 mr-2" />
                  {t("docker.disconnect")}
                </Button>
              )}
            </div>
          </div>
        </CardContent>
      </Card>

      <Card
        className="flex-1 overflow-hidden pt-1 pb-0"
        style={{ background: themeColors.background }}
      >
        <CardContent className="p-0 h-full relative">
          <div
            ref={xtermRef}
            className="h-full w-full"
            style={{ display: isConnected ? "block" : "none" }}
          />

          {!isConnected &&
            !isConnecting &&
            retry.status !== "error" &&
            retry.status !== "disconnected" && (
              <div className="absolute inset-0 flex items-center justify-center">
                <div className="text-center space-y-2">
                  <TerminalIcon className="h-12 w-12 text-muted-foreground/50 mx-auto" />
                  <p className="text-muted-foreground">
                    {t("docker.notConnected")}
                  </p>
                  <p className="text-muted-foreground text-sm">
                    {t("docker.clickToConnect")}
                  </p>
                </div>
              </div>
            )}

          {!isConnected &&
            (isConnecting ||
              retry.status === "error" ||
              retry.status === "disconnected") && (
              <ConnectionScreen
                status={isConnecting ? "connecting" : retry.status}
                message={t("docker.connectingTo", { containerName })}
                attempt={retry.attempt}
                maxAttempts={retry.maxAttempts}
                nextRetryInMs={retry.nextRetryInMs}
                onManualRetry={() => {
                  clearLogs();
                  retry.retryNow();
                }}
                retryLabel={t("docker.connect")}
              />
            )}
        </CardContent>
      </Card>
    </div>
  );
}
