import type {
  AppConfig,
  GatewayStatus,
  RuntimeCloudFetchAction,
  RuntimeStatus
} from "@qjclaw/shared-types";
import { isTransientLocalGatewayError } from "./workspace-startup.js";

export interface ChatGatewayRecoveryCoordinator {
  getConfig: () => Promise<AppConfig>;
  runtimeStatus: () => Promise<RuntimeStatus>;
  gatewayStatus: () => Promise<GatewayStatus | null>;
  startManagedRuntime: (
    reason: string,
    options?: {
      action?: RuntimeCloudFetchAction;
      restart?: boolean;
      config?: AppConfig;
      inputToken?: string;
    }
  ) => Promise<RuntimeStatus>;
  shouldRefreshGatewayClient: (config?: AppConfig, inputToken?: string) => Promise<boolean>;
  reconfigureGatewayClient: (config?: AppConfig, inputToken?: string) => Promise<void>;
  connectGatewayClientWithRetry: (mode?: "external" | "bundled") => Promise<void>;
}

function isManagedRuntimeMode(config: AppConfig): boolean {
  return config.runtimeMode !== "external-gateway";
}

function isGatewayConnected(status: GatewayStatus | null): boolean {
  return status?.state === "connected";
}

function toRuntimeNotReadyError(status: RuntimeStatus): Error {
  return new Error(status.lastError ?? status.message ?? "Local assistant is not ready.");
}

function toGatewayNotReadyError(status: GatewayStatus | null): Error {
  return new Error(status?.lastError ?? status?.message ?? "Gateway is not connected.");
}

export function isRecoverableGatewayChatError(message?: string): boolean {
  if (!message) {
    return false;
  }

  const normalized = message.toLowerCase();
  return normalized.includes("gateway websocket is not open")
    || isTransientLocalGatewayError(message);
}

export async function ensureGatewayReadyForChat(
  coordinator: ChatGatewayRecoveryCoordinator,
  reason: string,
  inputToken?: string
): Promise<void> {
  const config = await coordinator.getConfig();
  let runtimeStatus = await coordinator.runtimeStatus();
  const managedRuntimeMode = isManagedRuntimeMode(config);

  if (managedRuntimeMode && runtimeStatus.processState !== "running") {
    runtimeStatus = await coordinator.startManagedRuntime(reason, {
      action: "init",
      config,
      inputToken
    });
  }

  const shouldRefreshGatewayClient = await coordinator.shouldRefreshGatewayClient(config, inputToken);
  if (shouldRefreshGatewayClient) {
    await coordinator.reconfigureGatewayClient(config, inputToken);
  }

  let gatewayStatus = await coordinator.gatewayStatus();
  if (!isGatewayConnected(gatewayStatus) || shouldRefreshGatewayClient) {
    await coordinator.connectGatewayClientWithRetry(managedRuntimeMode ? "bundled" : "external");
    gatewayStatus = await coordinator.gatewayStatus();
  }

  runtimeStatus = await coordinator.runtimeStatus();
  if (managedRuntimeMode && runtimeStatus.processState !== "running") {
    throw toRuntimeNotReadyError(runtimeStatus);
  }

  if (!isGatewayConnected(gatewayStatus)) {
    throw toGatewayNotReadyError(gatewayStatus);
  }
}

export async function recoverGatewayForChat(
  coordinator: ChatGatewayRecoveryCoordinator,
  _reason: string,
  inputToken?: string
): Promise<void> {
  const config = await coordinator.getConfig();
  await coordinator.reconfigureGatewayClient(config, inputToken);
  await coordinator.connectGatewayClientWithRetry(isManagedRuntimeMode(config) ? "bundled" : "external");

  const runtimeStatus = await coordinator.runtimeStatus();
  if (isManagedRuntimeMode(config) && runtimeStatus.processState !== "running") {
    throw toRuntimeNotReadyError(runtimeStatus);
  }

  const gatewayStatus = await coordinator.gatewayStatus();
  if (!isGatewayConnected(gatewayStatus)) {
    throw toGatewayNotReadyError(gatewayStatus);
  }
}

export async function runGatewayChatRequestWithRecovery<T>(
  coordinator: ChatGatewayRecoveryCoordinator,
  input: {
    reason: string;
    inputToken?: string;
    execute: () => Promise<T>;
  }
): Promise<T> {
  await ensureGatewayReadyForChat(coordinator, input.reason, input.inputToken);
  try {
    return await input.execute();
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    if (!isRecoverableGatewayChatError(message)) {
      throw error;
    }

    await recoverGatewayForChat(coordinator, `${input.reason}-recover`, input.inputToken);
    return input.execute();
  }
}
