/*
 * SPEC: PJ2026-0104010803 Workbench唯一投影; HWLAB#1832 Workbench runtime Go 微服务拆分.
 * 职责: Cloud API 到 hwlab-workbench-runtime 的单一路径只读客户端；配置后不回退本地 PG。
 */

const DEFAULT_WORKBENCH_RUNTIME_TIMEOUT_MS = 10000;

export function createWorkbenchRuntimeClient(options = {}) {
  const env = options.env ?? process.env;
  const baseUrl = normalizeBaseUrl(env.HWLAB_WORKBENCH_RUNTIME_URL);
  if (!baseUrl) return null;
  const timeoutMs = boundedInteger(env.HWLAB_WORKBENCH_RUNTIME_TIMEOUT_MS, DEFAULT_WORKBENCH_RUNTIME_TIMEOUT_MS, 1000, 60000);
  const fetchFn = options.fetch ?? globalThis.fetch;
  const traceparent = normalizeTraceparent(options.traceparent ?? options.traceParent);
  return {
    serviceId: "hwlab-workbench-runtime",
    queryWorkbenchFacts: (params = {}) => postRuntime({ fetchFn, baseUrl, timeoutMs, traceparent, path: "/v1/workbench-runtime/facts", body: params }),
    queryAgentTraceEvents: (params = {}) => postRuntime({ fetchFn, baseUrl, timeoutMs, traceparent, path: "/v1/workbench-runtime/trace-events", body: params }),
    listWorkbenchSessions: (params = {}) => postRuntime({ fetchFn, baseUrl, timeoutMs, traceparent, path: "/v1/workbench-runtime/sessions", body: params }),
    readWorkbenchProjectionOutbox: (params = {}) => postRuntime({ fetchFn, baseUrl, timeoutMs, traceparent, path: "/v1/workbench-runtime/projection-outbox", body: params }).then((result) => Array.isArray(result?.rows) ? result.rows : [])
  };
}

async function postRuntime({ fetchFn, baseUrl, timeoutMs, traceparent, path, body }) {
  if (typeof fetchFn !== "function") {
    throw runtimeDependencyError({ code: "workbench_runtime_fetch_unavailable", message: "fetch is unavailable for workbench runtime dependency", retryable: false });
  }
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  let response;
  try {
    const headers = { "content-type": "application/json", accept: "application/json" };
    if (traceparent) headers.traceparent = traceparent;
    response = await fetchFn(new URL(path, baseUrl), {
      method: "POST",
      headers,
      body: JSON.stringify(body ?? {}),
      signal: controller.signal
    });
  } catch (error) {
    throw runtimeDependencyError({ code: error?.name === "AbortError" ? "workbench_runtime_timeout" : "workbench_runtime_unreachable", message: error?.message ?? "workbench runtime request failed", retryable: true, cause: error });
  } finally {
    clearTimeout(timer);
  }
  const payload = await responseJson(response);
  if (!response.ok) {
    const error = payload?.error ?? {};
    throw runtimeDependencyError({
      code: error.code ?? `workbench_runtime_http_${response.status}`,
      message: error.message ?? `workbench runtime request failed with HTTP ${response.status}`,
      retryable: response.status >= 500 || error.retryable === true,
      status: response.status,
      payload
    });
  }
  return payload;
}

async function responseJson(response) {
  try { return await response.json(); } catch { return null; }
}

function normalizeTraceparent(value) {
  const text = String(value ?? "").trim().toLowerCase();
  return /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/u.test(text) ? text : "";
}

function runtimeDependencyError({ code, message, retryable, status = null, payload = null, cause = null }) {
  const error = new Error(message || code || "workbench runtime dependency failed");
  error.name = "WorkbenchRuntimeDependencyError";
  error.code = code ?? "workbench_runtime_dependency_failed";
  if (cause) error.cause = cause;
  error.data = {
    serviceId: "hwlab-workbench-runtime",
    status,
    retryable: retryable === true,
    transient: retryable === true,
    payload,
    valuesRedacted: true
  };
  return error;
}

function normalizeBaseUrl(value) {
  const text = String(value ?? "").trim();
  if (!text) return null;
  return text.endsWith("/") ? text : `${text}/`;
}

function boundedInteger(value, fallback, min, max) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) return fallback;
  return Math.max(min, Math.min(max, Math.trunc(parsed)));
}
