import type { JsonValue } from "../../shared/src/index";
import { ctx, config, logger } from "./context";
import type { HttpTunnelFailureReason, MicroserviceConfig, MicroserviceProxyCacheEntry, MicroserviceHealthAssessment, MicroserviceAvailabilityEntry, RawTaskRow } from "./types";
import { jsonResponse, errorToJson, compactJson, isPlainRecord, truncateText } from "./http";
import { createAndSendTask, waitForTaskTerminal, providerSupports } from "./task-dispatcher";
import { getNodes, getNodeDockerStatuses } from "./db";

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const microserviceProxyMaxBodyTextLength = 8 * 1024 * 1024;
const microserviceAvailabilityTtlMs = 30_000;
const codeQueueOverviewPathFallbackStaleMs = 30_000;
const providerHttpTunnelMaxAttempts = 3;
const microserviceForwardRequestHeaders = [
  "accept",
  "content-type",
  "cookie",
  "range",
  "x-auth",
  "x-requested-with",
  "destination",
  "overwrite",
  "tus-resumable",
  "upload-concat",
  "upload-defer-length",
  "upload-length",
  "upload-metadata",
  "upload-offset",
] as const;
const microserviceForwardResponseHeaders = [
  "cache-control",
  "content-disposition",
  "etag",
  "expires",
  "last-modified",
  "location",
  "set-cookie",
  "www-authenticate",
] as const;

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

function recordValue(value: unknown, key: string): unknown {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
  return (value as Record<string, unknown>)[key];
}

function dockerStatusRecord(status: JsonValue | null): Record<string, unknown> {
  return typeof status === "object" && status !== null && !Array.isArray(status) ? status as Record<string, unknown> : {};
}

function findContainer(status: JsonValue | null, containerName: string): JsonValue | null {
  const containers = dockerStatusRecord(status).containers;
  if (!Array.isArray(containers)) return null;
  const container = containers.find((item) => {
    if (typeof item !== "object" || item === null || Array.isArray(item)) return false;
    return (item as Record<string, unknown>).name === containerName;
  });
  return container === undefined ? null : container as JsonValue;
}

function cachedMicroserviceAvailability(serviceId: string): Record<string, JsonValue> | null {
  const entry = ctx.microserviceAvailabilityCache.get(serviceId);
  if (entry === undefined) return null;
  if (entry.expiresAt <= Date.now()) {
    ctx.microserviceAvailabilityCache.delete(serviceId);
    return null;
  }
  return entry.probe;
}

function inferredMicroserviceAvailability(service: MicroserviceConfig, providerStatus: string, container: JsonValue | null): JsonValue {
  const cached = cachedMicroserviceAvailability(service.id);
  if (cached !== null) return cached;
  const containerState = isPlainRecord(container) ? String(container.state ?? "").toLowerCase() : "";
  const containerStatus = isPlainRecord(container) ? String(container.status ?? "") : "";
  if (providerStatus !== "online") {
    return {
      serviceId: service.id,
      healthy: false,
      status: "unhealthy",
      reason: `provider is ${providerStatus}`,
      source: "runtime-inference",
    };
  }
  if (container !== null && containerState !== "running") {
    return {
      serviceId: service.id,
      healthy: false,
      status: "unhealthy",
      reason: `container is ${containerStatus || containerState || "not running"}`,
      source: "runtime-inference",
    };
  }
  return {
    serviceId: service.id,
    healthy: null,
    status: "unknown",
    reason: "strict health has not been probed yet",
    source: "runtime-inference",
  };
}

function isMicroservicePathAllowed(service: MicroserviceConfig, path: string): boolean {
  return service.backend.allowedPathPrefixes.some((prefix) => path === prefix || path.startsWith(prefix));
}

function isMicroserviceMethodAllowed(service: MicroserviceConfig, method: string): boolean {
  return service.backend.allowedMethods.includes(method.toUpperCase());
}

function readMicroserviceArrayLimits(url: URL): { query: string; jsonArrayLimits: Record<string, JsonValue> } {
  const params = new URLSearchParams(url.searchParams);
  const jsonArrayLimits: Record<string, JsonValue> = {};
  const rawLimits = params.getAll("__unideskArrayLimit");
  params.delete("__unideskArrayLimit");
  for (const raw of rawLimits) {
    for (const entry of raw.split(",")) {
      const [path = "", limitText = ""] = entry.split(":");
      if (!/^[A-Za-z0-9_.-]+$/.test(path)) continue;
      const limit = Number(limitText);
      if (Number.isInteger(limit) && limit > 0 && limit <= 500) jsonArrayLimits[path] = limit;
    }
  }
  const search = params.toString();
  return { query: search.length > 0 ? `?${search}` : "", jsonArrayLimits };
}
function contentTypeIsJson(contentType: string): boolean {
  return contentType.toLowerCase().includes("json");
}

function readMicroserviceRequestHeaders(req: Request): Record<string, JsonValue> {
  const requestHeaders: Record<string, JsonValue> = {};
  for (const name of microserviceForwardRequestHeaders) {
    const value = req.headers.get(name);
    if (value !== null && value.length > 0) requestHeaders[name] = value.slice(0, 4096);
  }
  return requestHeaders;
}

function headersFromMicroserviceRequest(requestHeaders: Record<string, JsonValue>): Headers {
  const headers = new Headers();
  for (const name of microserviceForwardRequestHeaders) {
    const value = requestHeaders[name];
    if (typeof value === "string" && value.length > 0) headers.set(name, value);
  }
  return headers;
}

function copyForwardedResponseHeaders(from: Headers, to: Headers): void {
  for (const name of microserviceForwardResponseHeaders) {
    const value = from.get(name);
    if (value !== null && value.length > 0) to.set(name, value);
  }
}

function boundedMicroserviceBodyText(
  bodyText: string,
  contentType: string,
  metadata: { serviceId: string; targetPath: string; status: number; upstreamBodyBytes: number },
): { bodyText: string; truncated: boolean } {
  if (bodyText.length <= microserviceProxyMaxBodyTextLength) {
    return { bodyText, truncated: false };
  }
  if (contentTypeIsJson(contentType)) {
    return {
      bodyText: JSON.stringify({
        ok: false,
        error: "microservice proxy response body is too large",
        serviceId: metadata.serviceId,
        targetPath: metadata.targetPath,
        upstreamStatus: metadata.status,
        upstreamBodyBytes: metadata.upstreamBodyBytes,
        transformedBodyBytes: bodyText.length,
        responseBodyLimitBytes: microserviceProxyMaxBodyTextLength,
        hint: "Use a paged endpoint or tighten __unideskArrayLimit so the response stays below the proxy safety limit.",
      }),
      truncated: true,
    };
  }
  return { bodyText: truncateText(bodyText, microserviceProxyMaxBodyTextLength), truncated: true };
}

function arrayAtPath(value: unknown, path: string): JsonValue[] | null {
  let current: unknown = value;
  for (const part of path.split(".")) {
    if (typeof current !== "object" || current === null || Array.isArray(current)) return null;
    current = (current as Record<string, unknown>)[part];
  }
  return Array.isArray(current) ? current as JsonValue[] : null;
}
function applyJsonArrayLimits(bodyText: string, contentType: string, limits: Record<string, JsonValue>): string {
  const entries = Object.entries(limits);
  if (entries.length === 0 || !contentType.toLowerCase().includes("json")) return bodyText;
  try {
    const parsed = JSON.parse(bodyText) as unknown;
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return bodyText;
    const root = parsed as Record<string, unknown>;
    const applied: Record<string, JsonValue> = {};
    for (const [path, rawLimit] of entries) {
      const limit = Number(rawLimit);
      if (!Number.isInteger(limit) || limit <= 0 || limit > 500) continue;
      const array = arrayAtPath(root, path);
      if (array === null) continue;
      const originalLength = array.length;
      if (array.length > limit) array.splice(limit);
      applied[path] = { limit, originalLength, returnedLength: array.length };
    }
    root._unidesk = { arrayLimits: applied };
    return JSON.stringify(parsed);
  } catch {
    return bodyText;
  }
}

function responseFromMicroserviceResult(task: RawTaskRow | null): Response {
  if (task === null) return jsonResponse({ ok: false, error: "microservice proxy task missing" }, 502);
  if (task.status !== "succeeded") return jsonResponse({ ok: false, error: "microservice proxy task failed", task }, 502);
  const result = dockerStatusRecord(task.result);
  return responseFromProviderMicroserviceResult(result, "provider-task");
}

function responseFromProviderMicroserviceResult(result: Record<string, unknown>, proxyMode: string): Response {
  const status = Number(result.status);
  const contentType = typeof result.contentType === "string" ? result.contentType : "application/json; charset=utf-8";
  const bodyText = typeof result.bodyText === "string" ? result.bodyText : "";
  if (!Number.isInteger(status) || status < 100 || status > 599) {
    return jsonResponse({ ok: false, error: "microservice proxy returned invalid upstream status", result }, 502);
  }
  if (result.truncated === true && contentTypeIsJson(contentType)) {
    try {
      JSON.parse(bodyText);
    } catch {
      return jsonResponse({
        ok: false,
        error: "microservice proxy response was truncated before a JSON boundary",
        upstreamStatus: status,
        upstreamBodyBytes: result.upstreamBodyBytes ?? null,
        returnedBodyBytes: result.returnedBodyBytes ?? bodyText.length,
        responseBodyLimitBytes: result.responseBodyLimitBytes ?? null,
        hint: "Upgrade the provider-gateway or request a smaller/paged microservice response.",
      }, 502);
    }
  }
  return new Response(bodyText, {
    status,
    headers: {
      "content-type": contentType,
      "x-unidesk-proxy-mode": proxyMode,
      "x-unidesk-upstream-proxy-mode": typeof result.proxyMode === "string" ? result.proxyMode : "",
      "x-unidesk-response-truncated": result.truncated === true ? "true" : "false",
    },
  });
}
function nestedRecord(value: unknown, key: string): Record<string, unknown> | null {
  if (!isPlainRecord(value)) return null;
  const child = value[key];
  return isPlainRecord(child) ? child : null;
}

function nestedBoolean(value: unknown, key: string): boolean | null {
  if (!isPlainRecord(value)) return null;
  const child = value[key];
  return typeof child === "boolean" ? child : null;
}

function nestedString(value: unknown, key: string): string | null {
  if (!isPlainRecord(value)) return null;
  const child = value[key];
  return typeof child === "string" ? child : null;
}

function jsonBodyFromText(bodyText: string, contentType: string): JsonValue {
  if (!contentTypeIsJson(contentType)) return bodyText.length > 0 ? truncateText(bodyText, 4000) : null;
  try {
    return compactJson(JSON.parse(bodyText) as unknown);
  } catch {
    return bodyText.length > 0 ? truncateText(bodyText, 4000) : null;
  }
}

function healthFailure(body: JsonValue, checks: Record<string, JsonValue>, reason: string): MicroserviceHealthAssessment {
  return { healthy: false, reason, checks, body };
}

function healthSuccess(body: JsonValue, checks: Record<string, JsonValue>, reason = "health endpoint returned a usable service state"): MicroserviceHealthAssessment {
  return { healthy: true, reason, checks, body };
}

function genericMicroserviceHealthAssessment(upstreamStatus: number, body: JsonValue): MicroserviceHealthAssessment {
  const checks: Record<string, JsonValue> = {
    upstreamStatus,
    upstream2xx: upstreamStatus >= 200 && upstreamStatus < 300,
  };
  if (upstreamStatus < 200 || upstreamStatus >= 300) {
    return healthFailure(body, checks, `health endpoint returned HTTP ${upstreamStatus}`);
  }
  if (isPlainRecord(body)) {
    const ok = body.ok;
    const success = body.success;
    const statusText = typeof body.status === "string" ? body.status : "";
    checks.okField = typeof ok === "boolean" ? ok : null;
    checks.successField = typeof success === "boolean" ? success : null;
    checks.statusField = statusText || null;
    if (ok === false) return healthFailure(body, checks, "health body ok=false");
    if (success === false) return healthFailure(body, checks, "health body success=false");
    if (["error", "failed", "fail", "unhealthy", "down", "offline", "not_ready", "not-ready"].includes(statusText.toLowerCase())) {
      return healthFailure(body, checks, `health body status=${statusText}`);
    }
  }
  return healthSuccess(body, checks);
}
function assessMetNonlinearHealth(upstreamStatus: number, body: JsonValue): MicroserviceHealthAssessment {
  const base = genericMicroserviceHealthAssessment(upstreamStatus, body);
  const checks = { ...base.checks };
  if (!base.healthy) return { ...base, checks };
  if (!isPlainRecord(body)) return healthFailure(body, checks, "MET Nonlinear health body is not JSON");
  checks.service = typeof body.service === "string" ? body.service : null;
  checks.queueReady = isPlainRecord(body.queue);
  const image = nestedRecord(body, "image");
  checks.mlImage = typeof image?.image === "string" ? image.image : null;
  checks.mlImagePresent = image?.present === true;
  if (body.service !== "met-nonlinear-unidesk-ts") {
    return healthFailure(body, checks, "MET Nonlinear health returned an unexpected service identity");
  }
  if (!isPlainRecord(body.queue)) return healthFailure(body, checks, "MET Nonlinear queue state is not available");
  if (image?.present !== true) return healthFailure(body, checks, "MET Nonlinear ML image is not ready");
  return healthSuccess(body, checks, "MET Nonlinear backend, queue, and ML image are ready");
}

function assessClaudeQqHealth(upstreamStatus: number, body: JsonValue): MicroserviceHealthAssessment {
  const base = genericMicroserviceHealthAssessment(upstreamStatus, body);
  const checks = { ...base.checks };
  if (!base.healthy) return { ...base, checks };
  if (!isPlainRecord(body)) return healthFailure(body, checks, "ClaudeQQ health body is not JSON");
  const napcat = nestedRecord(body, "napcat");
  const endpoints = Array.isArray(body.endpoints) ? body.endpoints.map((item) => String(item)) : [];
  const httpConnected = nestedBoolean(napcat, "httpConnected") === true;
  const wsConnected = nestedBoolean(napcat, "wsConnected") === true;
  const napcatConnected = nestedBoolean(napcat, "connected") === true;
  const loginState = nestedString(napcat, "loginState") ?? "";
  checks.service = typeof body.service === "string" ? body.service : null;
  checks.pushEndpoint = endpoints.includes("/api/push/text");
  checks.napcatHttpLoggedIn = httpConnected;
  checks.napcatWebsocketConnected = wsConnected;
  checks.napcatConnected = napcatConnected;
  checks.loginState = loginState || null;
  if (body.service !== "claudeqq") return healthFailure(body, checks, "ClaudeQQ health returned an unexpected service identity");
  if (!endpoints.includes("/api/push/text")) return healthFailure(body, checks, "ClaudeQQ push API endpoint is not advertised");
  if (!httpConnected) return healthFailure(body, checks, "NapCat HTTP is not logged in");
  if (!wsConnected) return healthFailure(body, checks, "NapCat OneBot WebSocket is not connected");
  if (!napcatConnected || loginState !== "logged_in") return healthFailure(body, checks, "NapCat is not in a logged-in send/receive ready state");
  return healthSuccess(body, checks, "ClaudeQQ and NapCat are logged in and send/receive ready");
}

function assessMicroserviceHealth(service: MicroserviceConfig, upstreamStatus: number, contentType: string, bodyText: string): MicroserviceHealthAssessment {
  const body = jsonBodyFromText(bodyText, contentType);
  if (service.id === "met-nonlinear") return assessMetNonlinearHealth(upstreamStatus, body);
  if (service.id === "claudeqq") return assessClaudeQqHealth(upstreamStatus, body);
  return genericMicroserviceHealthAssessment(upstreamStatus, body);
}
function healthProbeFromAssessment(
  service: MicroserviceConfig,
  upstreamStatus: number,
  contentType: string,
  assessment: MicroserviceHealthAssessment,
): Record<string, JsonValue> {
  return {
    serviceId: service.id,
    name: service.name,
    providerId: service.providerId,
    healthPath: service.backend.healthPath,
    healthy: assessment.healthy,
    status: assessment.healthy ? "healthy" : "unhealthy",
    reason: assessment.reason,
    upstreamStatus,
    contentType,
    checks: assessment.checks,
    checkedAt: new Date().toISOString(),
  };
}

function rememberMicroserviceAvailability(serviceId: string, probe: Record<string, JsonValue>): void {
  ctx.microserviceAvailabilityCache.set(serviceId, { expiresAt: Date.now() + microserviceAvailabilityTtlMs, probe });
}

function canDirectProxyMicroservice(service: MicroserviceConfig): boolean {
  return service.providerId === "main-server" || service.deployment.mode === "internal-sidecar" || service.backend.proxyMode === "cluster-service-http";
}

function isK3sctlManagedMicroservice(service: MicroserviceConfig): boolean {
  return service.deployment.mode === "k3sctl-managed" || service.backend.proxyMode === "k3sctl-adapter-http";
}

function codeQueueK3sServiceIdForRequest(method: string, targetPath: string): string {
  const normalizedMethod = method.toUpperCase();
  if (targetPath === "/" || targetPath === "/health" || targetPath === "/live" || targetPath === "/api/dev-ready" || targetPath === "/api/runtime-preflight") return "code-queue-scheduler";
  if (targetPath === "/api/queues" || targetPath === "/api/tasks/overview") return "code-queue-scheduler";
  if (targetPath === "/api/oa/backfill" || targetPath === "/api/notifications/claudeqq/drain" || targetPath === "/api/notifications/claudeqq/backfill") return "code-queue-scheduler";
  if (targetPath === "/api/judge/probe" || targetPath === "/api/judge/self-test" || targetPath === "/api/queue-order/self-test" || targetPath === "/api/reference-injection/self-test" || targetPath === "/api/trace-port/self-test") return "code-queue-scheduler";
  if (/^\/api\/tasks\/[^/]+\/(?:steer|resume|interrupt)$/u.test(targetPath)) return "code-queue-scheduler";
  if (/^\/api\/tasks\/[^/]+$/u.test(targetPath) && normalizedMethod === "DELETE") return "code-queue-scheduler";
  if (targetPath === "/api/dev-containers" || /^\/api\/dev-containers(?:\/[^/]+)?\/start$/u.test(targetPath)) return "code-queue-scheduler";
  if (/^\/api\/dev-containers(?:\/[^/]+)?\/status$/u.test(targetPath)) return "code-queue-scheduler";
  if (normalizedMethod === "GET" || normalizedMethod === "HEAD") return "code-queue-read";
  return "code-queue-write";
}

function devCodeQueueDirectBaseUrl(k3sServiceId: string): string | null {
  if (config().identity.environment !== "dev") return null;
  const enabled = String(process.env.CODE_QUEUE_DEV_DIRECT_PROXY ?? "1").toLowerCase();
  if (enabled === "0" || enabled === "false" || enabled === "no") return null;
  const namespace = config().identity.namespace || process.env.UNIDESK_NAMESPACE || "unidesk-dev";
  if (k3sServiceId === "code-queue-scheduler") {
    return (process.env.CODE_QUEUE_DEV_SCHEDULER_URL || `http://code-queue-scheduler-dev.${namespace}.svc.cluster.local:4222`).replace(/\/+$/u, "");
  }
  if (k3sServiceId === "code-queue-read") {
    return (process.env.CODE_QUEUE_DEV_READ_URL || `http://code-queue-read-dev.${namespace}.svc.cluster.local:4222`).replace(/\/+$/u, "");
  }
  if (k3sServiceId === "code-queue-write") {
    return (process.env.CODE_QUEUE_DEV_WRITE_URL || `http://code-queue-write-dev.${namespace}.svc.cluster.local:4222`).replace(/\/+$/u, "");
  }
  return null;
}

function devCodeQueueDirectService(service: MicroserviceConfig, method: string, targetPath: string): { service: MicroserviceConfig; k3sServiceId: string } | null {
  const k3sServiceId = codeQueueK3sServiceIdForRequest(method, targetPath);
  const nodeBaseUrl = devCodeQueueDirectBaseUrl(k3sServiceId);
  if (nodeBaseUrl === null) return null;
  return {
    k3sServiceId,
    service: {
      ...service,
      backend: {
        ...service.backend,
        nodeBaseUrl,
        proxyMode: "dev-k3s-direct",
      },
      deployment: {
        ...service.deployment,
        mode: "unidesk-direct",
        k3sServiceId,
      },
    },
  };
}

function codeQueueMasterControlPath(method: string, targetPath: string): boolean {
  const normalizedMethod = method.toUpperCase();
  if (targetPath === "/" || targetPath === "/health" || targetPath === "/live" || targetPath === "/logs") return true;
  if (targetPath === "/api/workdirs" || /^\/api\/workdirs\/[^/]+\/[^/]+\/.+$/u.test(targetPath)) return true;
  if (targetPath === "/api/queue-claim-move/self-test") return true;
  if (targetPath === "/api/queues" || targetPath === "/api/queues/merge") return true;
  if (/^\/api\/queues\/[^/]+(?:\/merge)?$/u.test(targetPath)) return true;
  if (targetPath === "/api/tasks" || targetPath === "/api/tasks/batch" || targetPath === "/api/tasks/overview" || targetPath === "/api/tasks/stats" || targetPath === "/api/tasks/read-all") return true;
  if (/^\/api\/tasks\/[^/]+\/(?:summary|trace-summary|trace-steps|trace-step|transcript|output|prompt|read|retry|move|edit)$/u.test(targetPath)) return true;
  if (/^\/api\/tasks\/[^/]+$/u.test(targetPath) && normalizedMethod === "GET") return true;
  return false;
}

function codeQueueCompatWorkdirsResponse(service: MicroserviceConfig, status: number, contentType: string, bodyText: string): Response | null {
  if (status !== 404) return null;
  let body: any = null;
  try {
    body = JSON.parse(bodyText);
  } catch {
    return null;
  }
  if (body?.path !== "/api/workdirs") return null;
  const providerId = "D601";
  return jsonResponse({
    ok: true,
    workdirs: [{
      providerId,
      executionMode: "default",
      path: "/workspace",
      createdAt: null,
      updatedAt: null,
      source: "backend-core-compat",
    }],
    defaultProviderId: providerId,
    defaultWorkdir: "/workspace",
    remoteDefaultWorkdir: "/home/ubuntu",
    source: "backend-core-compat-code-queue-workdirs",
    compat: {
      reason: "code-queue-mgr version does not yet expose /api/workdirs",
      upstreamStatus: status,
      upstreamContentType: contentType,
    },
  }, 200);
}

// ---------------------------------------------------------------------------
// Cache helpers
// ---------------------------------------------------------------------------

function microserviceCacheTtlMs(serviceId: string, targetPath: string): number {
  if (targetPath === "/health") return 0;
  if (targetPath.endsWith("/stream")) return 0;
  if (serviceId === "pipeline" && targetPath === "/api/snapshot") return 6_000;
  if (serviceId === "pipeline" && targetPath.startsWith("/api/oa-event-flow/")) return 20_000;
  if (serviceId === "pipeline" && targetPath.startsWith("/api/model-quota/")) return 60_000;
  if (serviceId === "pipeline" && targetPath.startsWith("/api/node-control/runs/")) return 6_000;
  if (serviceId === "pipeline" && targetPath.startsWith("/api/runs/")) return 6_000;
  if (serviceId === "findjob" && (targetPath === "/api/summary" || targetPath === "/api/jobs" || targetPath === "/api/drafts")) return 8_000;
  if (serviceId === "met-nonlinear" && (targetPath === "/api/images" || targetPath === "/api/projects")) return 15_000;
  if (serviceId === "met-nonlinear" && (targetPath === "/api/queue" || targetPath === "/api/summary" || targetPath === "/api/history")) return 5_000;
  if (serviceId === "code-queue" && targetPath.includes("/transcript")) return 1_000;
  if (serviceId === "code-queue" && targetPath === "/api/tasks/overview") return 3_000;
  if (serviceId === "code-queue" && targetPath.startsWith("/api/tasks/")) return 2_000;
  return 750;
}

function microserviceCacheStaleMs(serviceId: string, targetPath: string): number {
  if (serviceId === "pipeline" && targetPath.startsWith("/api/model-quota/")) return 5 * 60_000;
  if (serviceId === "pipeline") return 45_000;
  if (serviceId === "findjob") return 60_000;
  if (serviceId === "met-nonlinear" && (targetPath === "/api/images" || targetPath === "/api/projects")) return 5 * 60_000;
  if (serviceId === "met-nonlinear") return 45_000;
  if (serviceId === "code-queue") return 15_000;
  return 5_000;
}
function providerMicroserviceCacheTtlMs(serviceId: string, targetPath: string): number {
  if (targetPath === "/health") return 0;
  if (serviceId === "met-nonlinear" && (targetPath === "/api/images" || targetPath === "/api/projects")) return 60_000;
  if (serviceId === "met-nonlinear" && targetPath === "/api/history") return 10_000;
  if (serviceId === "met-nonlinear" && (targetPath === "/api/queue" || targetPath === "/api/summary")) return 3_000;
  if (serviceId === "pipeline" && (targetPath === "/api/snapshot" || targetPath.startsWith("/api/oa-event-flow/"))) return 2_000;
  if (serviceId === "findjob" && (targetPath === "/api/summary" || targetPath === "/api/jobs")) return 2_000;
  return 1_000;
}

function microserviceCacheKey(service: MicroserviceConfig, method: string, targetPath: string, proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> }): string {
  return JSON.stringify([service.id, method, targetPath, proxyOptions.query, proxyOptions.jsonArrayLimits]);
}

function microservicePathFallbackCacheKey(service: MicroserviceConfig, method: string, targetPath: string): string {
  return JSON.stringify([service.id, method, targetPath, "__path_fallback__"]);
}

function canUseMicroservicePathFallback(service: MicroserviceConfig, method: string, targetPath: string): boolean {
  return service.id === "code-queue" && targetPath === "/api/tasks/overview" && (method === "GET" || method === "HEAD");
}

function cloneMicroserviceCacheEntry(entry: MicroserviceProxyCacheEntry): MicroserviceProxyCacheEntry {
  return { ...entry };
}

function responseFromMicroserviceCache(entry: MicroserviceProxyCacheEntry, state: "hit" | "stale" | "path-stale"): Response {
  return new Response(entry.bodyText, {
    status: entry.status,
    headers: {
      "content-type": entry.contentType,
      "x-unidesk-cache": state,
    },
  });
}

function isMicroserviceTransientFailureResponse(response: Response): boolean {
  if (response.status !== 502 && response.status !== 503 && response.status !== 504) return false;
  return response.headers.get("x-unidesk-transient-error") === "true"
    || response.headers.get("x-unidesk-tunnel-error") !== null
    || response.headers.get("x-unidesk-upstream-proxy-mode") === "provider-gateway-http-fetch";
}

function readMicroserviceCache(key: string): Response | null {
  const entry = ctx.microserviceProxyCache.get(key);
  if (entry === undefined) return null;
  if (entry.staleExpiresAt <= Date.now()) {
    ctx.microserviceProxyCache.delete(key);
    return null;
  }
  if (entry.expiresAt <= Date.now()) return null;
  return responseFromMicroserviceCache(entry, "hit");
}

function readStaleMicroserviceCache(key: string): Response | null {
  const entry = ctx.microserviceProxyCache.get(key);
  if (entry === undefined) return null;
  if (entry.staleExpiresAt <= Date.now()) {
    ctx.microserviceProxyCache.delete(key);
    return null;
  }
  if (entry.expiresAt > Date.now()) return null;
  return responseFromMicroserviceCache(entry, "stale");
}

function readMicroservicePathFallback(service: MicroserviceConfig, method: string, targetPath: string): Response | null {
  if (!canUseMicroservicePathFallback(service, method, targetPath)) return null;
  const key = microservicePathFallbackCacheKey(service, method, targetPath);
  const entry = ctx.microserviceProxyCache.get(key);
  if (entry === undefined) return null;
  if (entry.staleExpiresAt <= Date.now()) {
    ctx.microserviceProxyCache.delete(key);
    return null;
  }
  return responseFromMicroserviceCache(entry, "path-stale");
}

async function cacheableResponseSnapshot(response: Response): Promise<MicroserviceProxyCacheEntry | null> {
  if (response.status < 200 || response.status >= 300) return null;
  if (response.headers.get("x-unidesk-response-truncated") === "true") return null;
  const bodyText = await response.clone().text();
  if (bodyText.length > 2 * 1024 * 1024) return null;
  return {
    expiresAt: 0,
    staleExpiresAt: 0,
    status: response.status,
    contentType: response.headers.get("content-type") ?? "application/octet-stream",
    bodyText,
  };
}
function rememberMicroserviceCache(key: string, ttlMs: number, entry: MicroserviceProxyCacheEntry | null): void {
  if (entry === null || ttlMs <= 0) return;
  const keyParts = JSON.parse(key) as string[];
  const stored = cloneMicroserviceCacheEntry(entry);
  stored.expiresAt = Date.now() + ttlMs;
  stored.staleExpiresAt = stored.expiresAt + microserviceCacheStaleMs(String(keyParts[0] ?? ""), String(keyParts[2] ?? ""));
  ctx.microserviceProxyCache.set(key, stored);
  if (ctx.microserviceProxyCache.size > 300) {
    const now = Date.now();
    for (const [cacheKey, cacheEntry] of ctx.microserviceProxyCache) {
      if (cacheEntry.expiresAt <= now || ctx.microserviceProxyCache.size > 240) ctx.microserviceProxyCache.delete(cacheKey);
    }
  }
}

function rememberMicroservicePathFallback(
  service: MicroserviceConfig,
  method: string,
  targetPath: string,
  entry: MicroserviceProxyCacheEntry | null,
): void {
  if (entry === null || !canUseMicroservicePathFallback(service, method, targetPath)) return;
  const now = Date.now();
  const stored = cloneMicroserviceCacheEntry(entry);
  stored.expiresAt = now;
  stored.staleExpiresAt = now + codeQueueOverviewPathFallbackStaleMs;
  ctx.microserviceProxyCache.set(microservicePathFallbackCacheKey(service, method, targetPath), stored);
}

function invalidateMicroserviceCache(serviceId: string): void {
  const prefix = `["${serviceId}",`;
  for (const key of ctx.microserviceProxyCache.keys()) {
    if (key.startsWith(prefix)) ctx.microserviceProxyCache.delete(key);
  }
}

// ---------------------------------------------------------------------------
// Upstream proxy functions
// ---------------------------------------------------------------------------

async function directMicroserviceResponse(
  service: MicroserviceConfig,
  method: string,
  targetPath: string,
  proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> },
  requestHeaders: Record<string, JsonValue>,
  bodyText: string,
  abortSignal?: AbortSignal,
): Promise<Response> {
  const baseUrl = new URL(service.backend.nodeBaseUrl);
  const upstreamUrl = new URL(targetPath, baseUrl);
  upstreamUrl.search = proxyOptions.query;
  const headers = headersFromMicroserviceRequest(requestHeaders);
  const streamExpected = method === "GET" && (targetPath.endsWith("/stream") || String(headers.get("accept") || "").toLowerCase().includes("text/event-stream"));
  const controller = new AbortController();
  const timer = streamExpected ? null : setTimeout(() => controller.abort(), Math.max(1000, service.backend.timeoutMs));
  try {
    const upstream = await fetch(upstreamUrl, {
      method,
      headers,
      body: method === "GET" || method === "HEAD" ? undefined : bodyText,
      signal: streamExpected ? abortSignal : controller.signal,
    });
    const upstreamContentType = upstream.headers.get("content-type") ?? "text/plain; charset=utf-8";
    if (upstreamContentType.toLowerCase().includes("text/event-stream")) {
      const responseHeaders = new Headers({
        "content-type": upstreamContentType,
        "cache-control": upstream.headers.get("cache-control") || "no-store, no-transform",
        "connection": "keep-alive",
        "x-unidesk-proxy-mode": "direct",
        "x-unidesk-response-truncated": "false",
      });
      const buffering = upstream.headers.get("x-accel-buffering");
      if (buffering !== null) responseHeaders.set("x-accel-buffering", buffering);
      copyForwardedResponseHeaders(upstream.headers, responseHeaders);
      return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
    }
    const rawBodyText = await upstream.text();
    const limitedBodyText = applyJsonArrayLimits(rawBodyText, upstreamContentType, proxyOptions.jsonArrayLimits);
    const bounded = boundedMicroserviceBodyText(limitedBodyText, upstreamContentType, {
      serviceId: service.id,
      targetPath,
      status: upstream.status,
      upstreamBodyBytes: rawBodyText.length,
    });
    const responseHeaders = new Headers({
      "content-type": upstreamContentType,
      "x-unidesk-proxy-mode": "direct",
      "x-unidesk-response-truncated": bounded.truncated ? "true" : "false",
    });
    copyForwardedResponseHeaders(upstream.headers, responseHeaders);
    return new Response(bounded.bodyText, {
      status: upstream.status,
      headers: responseHeaders,
    });
  } catch (error) {
    return jsonResponse({ ok: false, error: "direct microservice proxy failed", serviceId: service.id, detail: errorToJson(error) }, 502);
  } finally {
    if (timer !== null) clearTimeout(timer);
  }
}

async function k3sctlAdapterMicroserviceResponse(
  service: MicroserviceConfig,
  method: string,
  targetPath: string,
  proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> },
  requestHeaders: Record<string, JsonValue>,
  bodyText: string,
  abortSignal?: AbortSignal,
): Promise<Response> {
  const adapterServiceId = service.deployment.adapterServiceId ?? "k3sctl-adapter";
  const adapter = microserviceById(adapterServiceId);
  if (adapter === null) {
    return jsonResponse({ ok: false, error: `k3sctl adapter microservice not found: ${adapterServiceId}`, serviceId: service.id }, 502);
  }
  if (adapter.id === service.id || isK3sctlManagedMicroservice(adapter)) {
    return jsonResponse({ ok: false, error: "k3sctl adapter must be a UniDesk-direct microservice", serviceId: service.id, adapterServiceId }, 500);
  }
  const k3sServiceId = service.id === "code-queue"
    ? codeQueueK3sServiceIdForRequest(method, targetPath)
    : service.deployment.k3sServiceId ?? service.id;
  const adapterTargetPath = `/api/services/${encodeURIComponent(k3sServiceId)}/proxy${targetPath}`;
  return fetchMicroserviceUpstreamResponse(adapter, method, adapterTargetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
}

async function k3sctlManagedDiagnosticsResponse(service: MicroserviceConfig): Promise<Response> {
  const adapterServiceId = service.deployment.adapterServiceId ?? "k3sctl-adapter";
  const adapter = microserviceById(adapterServiceId);
  const checkedAt = new Date().toISOString();
  const providerId = adapter?.providerId ?? service.providerId;
  const providerOnline = ctx.activeProviders.has(providerId);
  const providerTunnelCapable = await providerSupports(providerId, "microservice.http.tunnel");
  if (adapter === null) {
    return jsonResponse({
      ok: false,
      serviceId: service.id,
      checkedAt,
      requestPath: "/diagnostics",
      checks: {
        providerGateway: { ok: providerOnline, providerId, online: providerOnline },
        httpTunnel: { ok: providerTunnelCapable, providerId, capable: providerTunnelCapable },
        k3sctlAdapter: { ok: false, serviceId: adapterServiceId, error: `k3sctl adapter microservice not found: ${adapterServiceId}` },
        kubernetesApiServiceProxy: { ok: false, skipped: true },
        targetService: { ok: false, skipped: true },
      },
    }, 502);
  }

  const k3sServiceId = service.id === "code-queue"
    ? codeQueueK3sServiceIdForRequest("GET", service.backend.healthPath)
    : service.deployment.k3sServiceId ?? service.id;
  const adapterPath = `/api/services/${encodeURIComponent(k3sServiceId)}/diagnostics`;
  const response = await fetchMicroserviceUpstreamResponse(
    adapter,
    "GET",
    adapterPath,
    { query: "", jsonArrayLimits: {} },
    { accept: "application/json" },
    "",
  );
  const contentType = response.headers.get("content-type") ?? "application/json; charset=utf-8";
  const bodyText = await response.text();
  let adapterBody: JsonValue = bodyText;
  try {
    adapterBody = JSON.parse(bodyText) as JsonValue;
  } catch {
    adapterBody = bodyText.slice(0, 4000);
  }
  const bodyRecord = isPlainRecord(adapterBody) ? adapterBody : {};
  const adapterChecks = isPlainRecord(bodyRecord.checks) ? bodyRecord.checks : {};
  const checks = {
    providerGateway: {
      ok: providerOnline,
      providerId,
      online: providerOnline,
      activeSocketCount: ctx.activeProviders.size,
    },
    httpTunnel: {
      ok: response.ok && response.headers.get("x-unidesk-proxy-mode") === "provider-ws-http-tunnel",
      providerId,
      capable: providerTunnelCapable,
      requestId: response.headers.get("x-unidesk-request-id") ?? null,
      attempts: response.headers.get("x-unidesk-http-tunnel-attempts") ?? null,
      upstreamProxyMode: response.headers.get("x-unidesk-upstream-proxy-mode") ?? null,
      proxyStatus: response.status,
    },
    k3sctlAdapter: {
      ok: response.ok,
      serviceId: adapter.id,
      providerId: adapter.providerId,
      status: response.status,
      contentType,
    },
    kubernetesApiServiceProxy: compactJson(adapterChecks.kubernetesApiServiceProxy ?? { ok: false, skipped: true }),
    targetService: compactJson(adapterChecks.targetService ?? adapterChecks.managedService ?? { ok: false, skipped: true }),
  } satisfies Record<string, JsonValue>;
  const httpTunnelCheck = checks.httpTunnel as Record<string, JsonValue>;
  return jsonResponse({
    ok: response.ok && providerOnline && httpTunnelCheck.ok === true,
    serviceId: service.id,
    k3sServiceId,
    checkedAt,
    path: service.backend.healthPath,
    chain: "CLI/frontend -> backend-core -> provider-gateway HTTP tunnel -> k3sctl-adapter -> Kubernetes API service proxy -> k3s Service",
    checks,
    adapter: adapterBody,
  }, response.ok ? 200 : response.status);
}

async function microserviceTunnelSelfTestResponse(service: MicroserviceConfig): Promise<Response> {
  const tunnelService = isK3sctlManagedMicroservice(service)
    ? microserviceById(service.deployment.adapterServiceId ?? "k3sctl-adapter")
    : service;
  if (tunnelService === null) {
    return jsonResponse({ ok: false, serviceId: service.id, error: "tunnel service not found", adapterServiceId: service.deployment.adapterServiceId ?? null }, 502);
  }
  if (!(await providerSupports(tunnelService.providerId, "microservice.http.tunnel"))) {
    return jsonResponse({
      ok: false,
      serviceId: service.id,
      providerId: tunnelService.providerId,
      error: `provider does not declare microservice.http.tunnel capability: ${tunnelService.providerId}`,
    }, 409);
  }
  const probeService = {
    ...tunnelService,
    backend: {
      ...tunnelService.backend,
      nodeBaseUrl: "http://127.0.0.1:1",
      timeoutMs: 1000,
    },
  };
  const response = await providerHttpTunnelMicroserviceResponse(
    probeService,
    "GET",
    "/",
    { query: "", jsonArrayLimits: {} },
    { accept: "application/json" },
    "",
  );
  const headers = {
    requestId: response.headers.get("x-unidesk-request-id"),
    tunnelError: response.headers.get("x-unidesk-tunnel-error"),
    providerId: response.headers.get("x-unidesk-provider-id"),
    serviceId: response.headers.get("x-unidesk-service-id"),
    transient: response.headers.get("x-unidesk-transient-error"),
  };
  const bodyText = await response.text();
  let body: JsonValue = bodyText.slice(0, 4000);
  try {
    body = JSON.parse(bodyText) as JsonValue;
  } catch {
    // Keep bounded text body for malformed JSON diagnostics.
  }
  const bodyRecord = isPlainRecord(body) ? body : {};
  const hasRequestId = typeof bodyRecord.requestId === "string" && bodyRecord.requestId.length > 0;
  const hasStage = typeof bodyRecord.stage === "string" && bodyRecord.stage.length > 0;
  const expectedStatus = response.status === 502 || response.status === 504;
  const ok = expectedStatus && hasRequestId && hasStage && headers.requestId === bodyRecord.requestId;
  return jsonResponse({
    ok,
    serviceId: service.id,
    tunnelServiceId: tunnelService.id,
    providerId: tunnelService.providerId,
    expectedFailure: true,
    status: response.status,
    checks: {
      expectedStatus,
      bodyHasRequestId: hasRequestId,
      bodyHasStage: hasStage,
      headerHasRequestId: typeof headers.requestId === "string" && headers.requestId.length > 0,
      headerHasTunnelError: typeof headers.tunnelError === "string" && headers.tunnelError.length > 0,
    },
    headers,
    body,
  }, ok ? 200 : 502);
}

function providerHttpTunnelRequestId(providerId: string): string {
  return `${providerId}:http_${Date.now()}_${Math.random().toString(16).slice(2)}`;
}

function canRetryProviderHttpTunnel(method: string, targetPath: string): boolean {
  const normalizedMethod = method.toUpperCase();
  if (normalizedMethod !== "GET" && normalizedMethod !== "HEAD") return false;
  return !targetPath.endsWith("/stream");
}

function providerHttpTunnelWaitMs(service: MicroserviceConfig, attempt: number, retryable: boolean): number {
  const baseTimeoutMs = Math.max(1000, service.backend.timeoutMs);
  if (!retryable) return baseTimeoutMs + 3000;
  if (attempt === 1) return Math.min(baseTimeoutMs + 3000, Math.max(5000, Math.floor(baseTimeoutMs * 0.45)));
  if (attempt === 2) return Math.min(baseTimeoutMs + 3000, Math.max(6000, Math.floor(baseTimeoutMs * 0.7)));
  return baseTimeoutMs + 3000;
}

function tunnelErrorBody(
  service: MicroserviceConfig,
  requestId: string,
  error: string,
  stage: string,
  status: number,
  extra: Record<string, JsonValue> = {},
): Response {
  const response = jsonResponse({
    ok: false,
    error,
    stage,
    providerId: service.providerId,
    serviceId: service.id,
    requestId,
    ...extra,
  }, status);
  response.headers.set("x-unidesk-request-id", requestId);
  response.headers.set("x-unidesk-provider-id", service.providerId);
  response.headers.set("x-unidesk-service-id", service.id);
  response.headers.set("x-unidesk-tunnel-error", stage);
  if (status === 502 || status === 503 || status === 504) response.headers.set("x-unidesk-transient-error", "true");
  return response;
}

function providerHttpTunnelFailureStatus(reason: HttpTunnelFailureReason | null): number {
  if (reason === "aborted") return 499;
  if (reason === "provider-disconnected") return 503;
  if (reason === "send-failed") return 502;
  return 504;
}

function tunnelFailureRetryable(reason: HttpTunnelFailureReason | null): boolean {
  return reason === "timeout" || reason === "provider-disconnected" || reason === "send-failed";
}

async function waitForProviderHttpTunnelResponse(
  providerId: string,
  requestId: string,
  timeoutMs: number,
  abortSignal?: AbortSignal,
): Promise<{ message: { providerId: string; requestId: string; ok: boolean; result: JsonValue } | null; reason: HttpTunnelFailureReason | null }> {
  return await new Promise((resolve) => {
    let settled = false;
    let abortHandler: (() => void) | null = null;
    const timer = setTimeout(() => settle(null, "timeout"), Math.max(1, timeoutMs));
    const settle = (
      message: { providerId: string; requestId: string; ok: boolean; result: JsonValue } | null,
      reason: HttpTunnelFailureReason | null = null,
    ): void => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      if (abortHandler !== null) abortSignal?.removeEventListener("abort", abortHandler);
      ctx.httpTunnelWaiters.delete(requestId);
      resolve({ message, reason });
    };
    abortHandler = () => settle(null, "aborted");
    if (abortSignal !== undefined) {
      if (abortSignal.aborted) {
        settle(null, "aborted");
        return;
      }
      abortSignal.addEventListener("abort", abortHandler, { once: true });
    }
    ctx.httpTunnelWaiters.set(requestId, (message, reason) => {
      if (message !== null && message.providerId !== providerId) {
        logger("warn", "http_tunnel_provider_mismatch", { requestId, expectedProviderId: providerId, actualProviderId: message.providerId });
        settle(null, "provider-mismatch");
        return;
      }
      settle(message, reason ?? null);
    });
  });
}

async function providerHttpTunnelMicroserviceResponse(
  service: MicroserviceConfig,
  method: string,
  targetPath: string,
  proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> },
  requestHeaders: Record<string, JsonValue>,
  bodyText: string,
  abortSignal?: AbortSignal,
): Promise<Response> {
  const retryable = canRetryProviderHttpTunnel(method, targetPath);
  const maxAttempts = retryable ? providerHttpTunnelMaxAttempts : 1;
  const attempts: JsonValue[] = [];
  let lastRequestId = "";
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const socket = ctx.activeProviders.get(service.providerId);
    const requestId = providerHttpTunnelRequestId(service.providerId);
    lastRequestId = requestId;
    if (socket === undefined) {
      attempts.push({ attempt, requestId, ok: false, reason: "provider-offline" });
      return tunnelErrorBody(service, requestId, `provider is offline: ${service.providerId}`, "provider-gateway-online", 503, {
        retryable,
        attempts,
      });
    }
    const timeoutMs = providerHttpTunnelWaitMs(service, attempt, retryable);
    const startedAt = Date.now();
    const waiter = waitForProviderHttpTunnelResponse(service.providerId, requestId, timeoutMs, abortSignal);
    try {
      socket.send(JSON.stringify({
        type: "http_tunnel_request",
        requestId,
        payload: {
          source: "microservice-frontend-proxy",
          serviceId: service.id,
          method,
          targetBaseUrl: service.backend.nodeBaseUrl,
          path: targetPath,
          query: proxyOptions.query,
          requestHeaders,
          bodyText,
          jsonArrayLimits: proxyOptions.jsonArrayLimits,
          timeoutMs: service.backend.timeoutMs,
          cacheTtlMs: providerMicroserviceCacheTtlMs(service.id, targetPath),
        },
      }));
    } catch (error) {
      ctx.httpTunnelWaiters.get(requestId)?.(null, "send-failed");
      const durationMs = Date.now() - startedAt;
      attempts.push({ attempt, requestId, ok: false, reason: "send-failed", durationMs, error: errorToJson(error) });
      if (attempt < maxAttempts) {
        logger("warn", "http_tunnel_send_retry", { providerId: service.providerId, serviceId: service.id, requestId, attempt, maxAttempts, error: errorToJson(error) });
        await Bun.sleep(Math.min(500, 75 * attempt));
        continue;
      }
      return tunnelErrorBody(service, requestId, "provider HTTP tunnel send failed", "http-tunnel-send", 502, {
        retryable,
        attempts,
        detail: errorToJson(error),
      });
    }
    const { message, reason } = await waiter;
    const durationMs = Date.now() - startedAt;
    if (message === null) {
      attempts.push({ attempt, requestId, ok: false, reason: reason ?? "timeout", durationMs, timeoutMs });
      if (retryable && reason !== "timeout" && tunnelFailureRetryable(reason) && attempt < maxAttempts) {
        logger("warn", "http_tunnel_retry", {
          providerId: service.providerId,
          serviceId: service.id,
          requestId,
          attempt,
          maxAttempts,
          reason: reason ?? "timeout",
          durationMs,
          timeoutMs,
        });
        await Bun.sleep(Math.min(750, 100 * attempt));
        continue;
      }
      return tunnelErrorBody(
        service,
        requestId,
        "provider HTTP tunnel timed out or disconnected",
        reason === "provider-disconnected" ? "http-tunnel-provider-disconnected" : reason === "aborted" ? "client-aborted" : "http-tunnel-wait",
        providerHttpTunnelFailureStatus(reason),
        { retryable, attempts, timeoutMs, failureReason: reason ?? "timeout" },
      );
    }
    attempts.push({ attempt, requestId, ok: message.ok, durationMs });
    if (!message.ok) {
      const result = dockerStatusRecord(message.result);
      const resultError = typeof result.error === "string" ? result.error : "provider HTTP tunnel failed";
      logger("warn", "http_tunnel_provider_error", {
        providerId: service.providerId,
        serviceId: service.id,
        requestId,
        attempt,
        maxAttempts,
        durationMs,
        result: compactJson(result),
      });
      if (retryable && attempt < maxAttempts) {
        await Bun.sleep(Math.min(750, 100 * attempt));
        continue;
      }
      return tunnelErrorBody(service, requestId, "provider HTTP tunnel failed", "provider-gateway-http-fetch", 502, {
        retryable,
        attempts,
        result: message.result,
        providerError: resultError,
      });
    }
    const response = responseFromProviderMicroserviceResult(dockerStatusRecord(message.result), "provider-ws-http-tunnel");
    response.headers.set("x-unidesk-request-id", requestId);
    response.headers.set("x-unidesk-http-tunnel-attempt", String(attempt));
    response.headers.set("x-unidesk-http-tunnel-attempts", String(attempts.length));
    response.headers.set("x-unidesk-provider-id", service.providerId);
    return response;
  }
  return tunnelErrorBody(service, lastRequestId, "provider HTTP tunnel exhausted attempts", "http-tunnel-wait", 504, { retryable, attempts });
}

async function fetchMicroserviceUpstreamResponse(
  service: MicroserviceConfig,
  method: string,
  targetPath: string,
  proxyOptions: { query: string; jsonArrayLimits: Record<string, JsonValue> },
  requestHeaders: Record<string, JsonValue>,
  bodyText: string,
  abortSignal?: AbortSignal,
): Promise<Response> {
  if (service.id === "code-queue") {
    const devDirect = devCodeQueueDirectService(service, method, targetPath);
    if (devDirect !== null) {
      const response = await directMicroserviceResponse(devDirect.service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
      response.headers.set("x-unidesk-code-queue-target", devDirect.k3sServiceId);
      return response;
    }
  }
  if (service.id === "code-queue" && codeQueueMasterControlPath(method, targetPath)) {
    const mgr = microserviceById("code-queue-mgr");
    if (mgr !== null) {
      const response = await directMicroserviceResponse(mgr, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
      if (method.toUpperCase() === "GET" && targetPath === "/api/workdirs") {
        const contentType = response.headers.get("content-type") ?? "text/plain; charset=utf-8";
        const responseBody = await response.text();
        const compat = codeQueueCompatWorkdirsResponse(service, response.status, contentType, responseBody);
        if (compat !== null) return compat;
        return new Response(responseBody, {
          status: response.status,
          headers: {
            "content-type": contentType,
            "x-unidesk-proxy-mode": response.headers.get("x-unidesk-proxy-mode") ?? "direct",
            "x-unidesk-response-truncated": response.headers.get("x-unidesk-response-truncated") ?? "false",
          },
        });
      }
      return response;
    }
    logger("warn", "code_queue_mgr_missing_fallback_to_d601", { method, targetPath });
  }
  if (isK3sctlManagedMicroservice(service)) {
    return k3sctlAdapterMicroserviceResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
  }
  if (canDirectProxyMicroservice(service)) {
    return directMicroserviceResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
  }
  if (!(await providerSupports(service.providerId, "microservice.http"))) {
    return jsonResponse({ ok: false, error: `provider does not declare microservice.http capability: ${service.providerId}` }, 409);
  }
  if (await providerSupports(service.providerId, "microservice.http.tunnel")) {
    return providerHttpTunnelMicroserviceResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, abortSignal);
  }
  const { taskId, providerOnline } = await createAndSendTask(service.providerId, "microservice.http", {
    source: "microservice-frontend-proxy",
    serviceId: service.id,
    method,
    targetBaseUrl: service.backend.nodeBaseUrl,
    path: targetPath,
    query: proxyOptions.query,
    requestHeaders,
    bodyText,
    jsonArrayLimits: proxyOptions.jsonArrayLimits,
    timeoutMs: service.backend.timeoutMs,
    cacheTtlMs: providerMicroserviceCacheTtlMs(service.id, targetPath),
  });
  if (!providerOnline) return jsonResponse({ ok: false, error: `provider is offline: ${service.providerId}`, taskId }, 503);
  const task = await waitForTaskTerminal(taskId, service.backend.timeoutMs + 3000);
  return responseFromMicroserviceResult(task);
}

function refreshMicroserviceCacheInBackground(
  cacheKey: string,
  ttlMs: number,
  fetchResponse: () => Promise<Response>,
  fallbackContext?: { service: MicroserviceConfig; method: string; targetPath: string },
  refreshKey = cacheKey,
): void {
  if (ctx.microserviceProxyRefreshes.has(refreshKey)) return;
  const refresh = fetchResponse()
    .then((response) => cacheableResponseSnapshot(response))
    .then((entry) => {
      rememberMicroserviceCache(cacheKey, ttlMs, entry);
      if (fallbackContext !== undefined) rememberMicroservicePathFallback(fallbackContext.service, fallbackContext.method, fallbackContext.targetPath, entry);
    })
    .catch((error) => {
      logger("warn", "microservice_cache_revalidate_failed", { cacheKey, error: errorToJson(error) });
    })
    .finally(() => {
      ctx.microserviceProxyRefreshes.delete(refreshKey);
    });
  ctx.microserviceProxyRefreshes.set(refreshKey, refresh);
}

// ---------------------------------------------------------------------------
// Exported functions
// ---------------------------------------------------------------------------

export function microserviceById(serviceId: string): MicroserviceConfig | null {
  return config().microservices.find((service) => service.id === serviceId) ?? null;
}

export async function strictMicroserviceHealthProbe(service: MicroserviceConfig, timeoutMs?: number): Promise<Record<string, JsonValue>> {
  const probeService = timeoutMs === undefined
    ? service
    : { ...service, backend: { ...service.backend, timeoutMs } };
  const response = await fetchMicroserviceUpstreamResponse(probeService, "GET", service.backend.healthPath, { query: "", jsonArrayLimits: {} }, { accept: "application/json" }, "");
  const upstreamStatus = response.status;
  const contentType = response.headers.get("content-type") ?? "application/octet-stream";
  const bodyText = await response.text();
  const assessment = assessMicroserviceHealth(service, upstreamStatus, contentType, bodyText);
  const probe = healthProbeFromAssessment(service, upstreamStatus, contentType, assessment);
  rememberMicroserviceAvailability(service.id, probe);
  return { ...probe, body: assessment.body };
}

export async function strictMicroserviceHealthResponse(service: MicroserviceConfig, headOnly: boolean): Promise<Response> {
  const response = await fetchMicroserviceUpstreamResponse(service, "GET", service.backend.healthPath, { query: "", jsonArrayLimits: {} }, { accept: "application/json" }, "");
  const upstreamStatus = response.status;
  const contentType = response.headers.get("content-type") ?? "application/octet-stream";
  const bodyText = await response.text();
  const assessment = assessMicroserviceHealth(service, upstreamStatus, contentType, bodyText);
  const probe = healthProbeFromAssessment(service, upstreamStatus, contentType, assessment);
  rememberMicroserviceAvailability(service.id, probe);
  if (assessment.healthy) {
    return new Response(headOnly ? null : bodyText, {
      status: upstreamStatus,
      headers: {
        "content-type": contentType,
        "x-unidesk-health": "healthy",
        "x-unidesk-health-reason": assessment.reason,
      },
    });
  }
  const status = upstreamStatus >= 500 ? upstreamStatus : 503;
  return jsonResponse({
    ok: false,
    status: "unhealthy",
    serviceId: service.id,
    name: service.name,
    providerId: service.providerId,
    reason: assessment.reason,
    checkedAt: probe.checkedAt,
    checks: assessment.checks,
    upstream: {
      status: upstreamStatus,
      contentType,
      body: assessment.body,
    },
  }, status);
}

export async function microserviceAvailability(service: MicroserviceConfig): Promise<Record<string, JsonValue>> {
  const cached = cachedMicroserviceAvailability(service.id);
  if (cached !== null) return cached;
  const existing = ctx.microserviceAvailabilityRefreshes.get(service.id);
  if (existing !== undefined) return existing;
  const timeoutMs = Math.max(2500, Math.min(service.backend.timeoutMs, 10_000));
  const refresh = strictMicroserviceHealthProbe(service, timeoutMs)
    .catch((error) => {
      const probe = {
        serviceId: service.id,
        name: service.name,
        providerId: service.providerId,
        healthPath: service.backend.healthPath,
        healthy: false,
        status: "unhealthy",
        reason: error instanceof Error ? error.message : String(error),
        upstreamStatus: null,
        contentType: null,
        checks: {},
        checkedAt: new Date().toISOString(),
      } satisfies Record<string, JsonValue>;
      rememberMicroserviceAvailability(service.id, probe);
      return probe;
    })
    .finally(() => {
      ctx.microserviceAvailabilityRefreshes.delete(service.id);
    });
  ctx.microserviceAvailabilityRefreshes.set(service.id, refresh);
  return refresh;
}

export async function getMicroservices(): Promise<JsonValue[]> {
  const nodes = await getNodes();
  const dockerStatuses = await getNodeDockerStatuses();
  return config().microservices.map((service) => {
    const node = nodes.find((item) => item.providerId === service.providerId) ?? null;
    const docker = dockerStatuses.find((item) => item.providerId === service.providerId) ?? null;
    const k3sManaged = isK3sctlManagedMicroservice(service);
    const container = k3sManaged ? null : findContainer(docker?.dockerStatus ?? null, service.repository.containerName);
    return {
      ...service,
      runtime: {
        orchestrator: k3sManaged ? "k3sctl" : "unidesk-direct",
        providerStatus: node?.status ?? "missing",
        providerName: node?.name ?? service.providerId,
        providerLastHeartbeat: node?.lastHeartbeat ?? null,
        container,
        availability: inferredMicroserviceAvailability(service, node?.status ?? "missing", container),
        backendPortMapping: {
          providerId: service.providerId,
          node: `${service.backend.nodeBindHost}:${service.backend.nodePort}`,
          mainServerAccess: "frontend-only backend proxy",
          public: service.backend.public,
        },
      },
    } satisfies JsonValue;
  });
}
export async function microserviceRoute(req: Request, url: URL): Promise<Response> {
  const rest = url.pathname.slice("/api/microservices/".length);
  const slashIndex = rest.indexOf("/");
  const serviceId = decodeURIComponent(slashIndex === -1 ? rest : rest.slice(0, slashIndex));
  const suffix = slashIndex === -1 ? "" : rest.slice(slashIndex + 1);
  const service = microserviceById(serviceId);
  if (service === null) return jsonResponse({ ok: false, error: `microservice not found: ${serviceId}` }, 404);
  const method = req.method.toUpperCase();
  if ((suffix === "" || suffix === "status") && (method === "GET" || method === "HEAD")) {
    return jsonResponse({ ok: true, microservice: (await getMicroservices()).find((item) => recordValue(item, "id") === serviceId) ?? service });
  }

  const proxyPrefix = "proxy";
  const targetPath = suffix === "health"
    ? service.backend.healthPath
    : suffix === proxyPrefix
      ? "/"
      : suffix.startsWith(`${proxyPrefix}/`)
        ? `/${suffix.slice(proxyPrefix.length + 1)}`
        : suffix === "diagnostics"
          ? "/diagnostics"
          : suffix === "tunnel-self-test"
            ? "/tunnel-self-test"
        : "";
  if (targetPath.length === 0) return jsonResponse({ ok: false, error: "microservice route must be /status, /health, /diagnostics, /tunnel-self-test, or /proxy/<path>" }, 404);
  if (suffix === "health" && method !== "GET" && method !== "HEAD") {
    return jsonResponse({ ok: false, error: "microservice health only supports GET/HEAD" }, 405);
  }
  if (suffix === "diagnostics" && method !== "GET" && method !== "HEAD") {
    return jsonResponse({ ok: false, error: "microservice diagnostics only supports GET/HEAD" }, 405);
  }
  if (suffix === "tunnel-self-test" && method !== "GET" && method !== "HEAD") {
    return jsonResponse({ ok: false, error: "microservice tunnel self-test only supports GET/HEAD" }, 405);
  }
  if (!isMicroserviceMethodAllowed(service, method)) {
    return jsonResponse({ ok: false, error: "microservice method is not allowed", serviceId, method, allowedMethods: service.backend.allowedMethods }, 405);
  }
  if (suffix === "diagnostics") {
    if (!isK3sctlManagedMicroservice(service)) return strictMicroserviceHealthResponse(service, method === "HEAD");
    return k3sctlManagedDiagnosticsResponse(service);
  }
  if (suffix === "tunnel-self-test") return microserviceTunnelSelfTestResponse(service);
  if (!isMicroservicePathAllowed(service, targetPath)) {
    return jsonResponse({ ok: false, error: "microservice path is not allowed", serviceId, targetPath }, 403);
  }
  if (suffix === "health") return strictMicroserviceHealthResponse(service, method === "HEAD");
  const proxyOptions = readMicroserviceArrayLimits(url);
  const cacheKey = microserviceCacheKey(service, method, targetPath, proxyOptions);
  const cacheTtlMs = microserviceCacheTtlMs(service.id, targetPath);
  if (method === "GET" || method === "HEAD") {
    const cached = readMicroserviceCache(cacheKey);
    if (cached !== null) return cached;
  } else {
    invalidateMicroserviceCache(service.id);
  }
  const bodyText = method === "GET" || method === "HEAD" ? "" : await req.text();
  if (bodyText.length > 1024 * 1024) {
    return jsonResponse({ ok: false, error: "microservice request body is too large", maxBytes: 1024 * 1024 }, 413);
  }
  const requestHeaders = readMicroserviceRequestHeaders(req);
  if (method === "GET" || method === "HEAD") {
    const stale = readStaleMicroserviceCache(cacheKey);
    if (stale !== null) {
      refreshMicroserviceCacheInBackground(
        cacheKey,
        cacheTtlMs,
        () => fetchMicroserviceUpstreamResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText),
        { service, method, targetPath },
      );
      return stale;
    }
    const fallback = readMicroservicePathFallback(service, method, targetPath);
    if (fallback !== null) {
      refreshMicroserviceCacheInBackground(
        cacheKey,
        cacheTtlMs,
        () => fetchMicroserviceUpstreamResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText),
        { service, method, targetPath },
        microservicePathFallbackCacheKey(service, method, targetPath),
      );
      return fallback;
    }
  }
  const response = await fetchMicroserviceUpstreamResponse(service, method, targetPath, proxyOptions, requestHeaders, bodyText, req.signal);
  if ((method === "GET" || method === "HEAD") && isMicroserviceTransientFailureResponse(response)) {
    const stale = readStaleMicroserviceCache(cacheKey) ?? readMicroservicePathFallback(service, method, targetPath);
    if (stale !== null) {
      stale.headers.set("x-unidesk-cache", "stale-on-transient-failure");
      stale.headers.set("x-unidesk-stale-reason", String(response.status));
      return stale;
    }
  }
  if ((method === "GET" || method === "HEAD") && cacheTtlMs > 0) {
    const snapshot = await cacheableResponseSnapshot(response);
    rememberMicroserviceCache(cacheKey, cacheTtlMs, snapshot);
    rememberMicroservicePathFallback(service, method, targetPath, snapshot);
  }
  return response;
}
