import {
  authApi,
  getRemoteGuacamoleApi,
  handleApiError,
  isElectron,
} from "@/main-axios";
import type { AxiosInstance } from "axios";
import type { GuacamoleConfig } from "@/types/guacamole-config";
import { resolveRemoteHostId } from "@/lib/remote-server-api";

/**
 * The embedded desktop backend does not bundle guacd, which is why
 * resolveConnectionOrigin() pins RDP/VNC/Telnet to "remote". These calls have to
 * follow: asking the embedded backend reports the guacd *it* cannot reach,
 * rather than the one on the connected server that serves the session.
 */
function guacamoleApi(): AxiosInstance {
  return isElectron() ? getRemoteGuacamoleApi() : authApi;
}

export interface GuacamoleTokenRequest {
  protocol: "rdp" | "vnc" | "telnet";
  hostname: string;
  port?: number;
  username?: string;
  password?: string;
  domain?: string;
  security?: string;
  ignoreCert?: boolean;
  guacamoleConfig?: GuacamoleConfig;
}

export interface GuacamoleTokenResponse {
  token: string;
  guacamoleConnectionId?: string | null;
}

type GuacamoleConfigSource = {
  guacamoleConfig?: string | Record<string, unknown> | null;
};

export function parseGuacamoleConfig(
  config?: string | GuacamoleConfig | null,
): GuacamoleConfig {
  if (!config) return {};
  if (typeof config !== "string") return config;
  try {
    return JSON.parse(config) as GuacamoleConfig;
  } catch {
    return {};
  }
}

export function getGuacamoleDpi(
  source?: GuacamoleConfigSource,
): number | undefined {
  const config = source?.guacamoleConfig;
  if (!config) return undefined;

  let dpi: unknown;
  if (typeof config === "string") {
    try {
      dpi = JSON.parse(config).dpi;
    } catch {
      return undefined;
    }
  } else {
    dpi = config.dpi;
  }

  const parsedDpi = typeof dpi === "string" ? Number(dpi) : dpi;
  if (
    typeof parsedDpi !== "number" ||
    !Number.isFinite(parsedDpi) ||
    parsedDpi <= 0
  ) {
    return undefined;
  }

  return Math.trunc(parsedDpi);
}

function toGuacamoleParams(
  config: GuacamoleTokenRequest["guacamoleConfig"],
): Record<string, unknown> {
  if (!config) return {};

  const params: Record<string, unknown> = {};

  const mappings: Record<string, string> = {
    colorDepth: "color-depth",
    resizeMethod: "resize-method",
    forceLossless: "force-lossless",
    disableAudio: "disable-audio",
    enableAudioInput: "enable-audio-input",
    enableWallpaper: "enable-wallpaper",
    enableTheming: "enable-theming",
    enableFontSmoothing: "enable-font-smoothing",
    enableFullWindowDrag: "enable-full-window-drag",
    enableDesktopComposition: "enable-desktop-composition",
    enableMenuAnimations: "enable-menu-animations",
    disableBitmapCaching: "disable-bitmap-caching",
    disableOffscreenCaching: "disable-offscreen-caching",
    disableGlyphCaching: "disable-glyph-caching",
    disableGfx: "disable-gfx",
    enablePrinting: "enable-printing",
    printerName: "printer-name",
    enableDrive: "enable-drive",
    driveName: "drive-name",
    drivePath: "drive-path",
    createDrivePath: "create-drive-path",
    disableDownload: "disable-download",
    disableUpload: "disable-upload",
    enableTouch: "enable-touch",
    clientName: "client-name",
    initialProgram: "initial-program",
    serverLayout: "server-layout",
    gatewayHostname: "gateway-hostname",
    gatewayPort: "gateway-port",
    gatewayUsername: "gateway-username",
    gatewayPassword: "gateway-password",
    gatewayDomain: "gateway-domain",
    remoteApp: "remote-app",
    remoteAppDir: "remote-app-dir",
    remoteAppArgs: "remote-app-args",
    normalizeClipboard: "normalize-clipboard",
    disableCopy: "disable-copy",
    disablePaste: "disable-paste",
    swapRedBlue: "swap-red-blue",
    readOnly: "read-only",
    recordingPath: "recording-path",
    recordingName: "recording-name",
    createRecordingPath: "create-recording-path",
    recordingExcludeOutput: "recording-exclude-output",
    recordingExcludeMouse: "recording-exclude-mouse",
    recordingIncludeKeys: "recording-include-keys",
    wolSendPacket: "wol-send-packet",
    wolMacAddr: "wol-mac-addr",
    wolBroadcastAddr: "wol-broadcast-addr",
    wolUdpPort: "wol-udp-port",
    wolWaitTime: "wol-wait-time",
  };

  for (const [key, value] of Object.entries(config)) {
    if (value !== undefined && value !== null && value !== "") {
      const paramName = mappings[key] || key;
      if (typeof value === "boolean") {
        params[paramName] = value ? "true" : "false";
      } else {
        params[paramName] = value;
      }
    }
  }

  return params;
}

export async function getGuacamoleToken(
  request: GuacamoleTokenRequest,
): Promise<GuacamoleTokenResponse> {
  try {
    const guacParams = toGuacamoleParams(request.guacamoleConfig);

    const response = await guacamoleApi().post("/guacamole/token", {
      type: request.protocol,
      hostname: request.hostname,
      port: request.port,
      username: request.username,
      password: request.password,
      domain: request.domain,
      security: request.security,
      "ignore-cert": request.ignoreCert,
      ...guacParams,
    });
    return response.data;
  } catch (error) {
    throw handleApiError(error, "get guacamole token");
  }
}

export async function getGuacamoleTokenFromHost(
  hostId: number,
  protocol?: "rdp" | "vnc" | "telnet",
  promptedCredentials?: {
    username?: string;
    password?: string;
    domain?: string;
  },
  syncId?: string | null,
): Promise<GuacamoleTokenResponse> {
  try {
    const remoteHostId = isElectron()
      ? await resolveRemoteHostId(syncId)
      : null;
    if (isElectron() && syncId && remoteHostId === null) {
      throw new Error("The synced host does not exist on the remote server");
    }
    const targetHostId = remoteHostId ?? hostId;
    const response = await guacamoleApi().post(
      `/guacamole/connect-host/${targetHostId}`,
      {
        ...(protocol ? { protocol } : {}),
        ...(promptedCredentials?.username
          ? { promptedUsername: promptedCredentials.username }
          : {}),
        ...(promptedCredentials?.password
          ? { promptedPassword: promptedCredentials.password }
          : {}),
        ...(promptedCredentials
          ? { promptedDomain: promptedCredentials.domain ?? "" }
          : {}),
      },
    );
    return response.data;
  } catch (error) {
    throw handleApiError(error, "get guacamole token from host");
  }
}

export async function getGuacdStatus(): Promise<{
  guacd: { status: string };
}> {
  const response = await guacamoleApi().get("/guacamole/status");
  return response.data;
}
