/*
 * SPEC: PJ2026-010405 云端控制台；PJ2026-010103 HWPOD 服务。
 * Implementation reference: draft-2026-07-13-p0-cloud-console。
 * Responsibility: 提供 HWLAB Node 更新、HWPOD typed topology、显式有界 readiness 探测与 node-ops 转发。
 */
import { randomUUID } from "node:crypto";

import { ENVIRONMENT_DEV } from "../protocol/index.mjs";
import { CLOUD_API_SERVICE_ID } from "../audit/index.mjs";
import {
  discoverHwpodSpecs,
  hwpodSpecDiscoveryPayload,
  hwpodSpecWorkspaceProbePlan
} from "./hwpod-spec-discovery.ts";
import { buildHwpodTopologyReadModel } from "./hwpod-topology-read-model.ts";
import { buildHwlabNodeUpdatePayload, hwlabNodeBundledArtifact } from "../hwpod/node-artifact.ts";
import {
  getHeader,
  parsePositiveInteger,
  readBody,
  safeOpaqueId,
  sendJson,
  truthyFlag
} from "./server-http-utils.ts";
import { HWPOD_NODE_OPS, HWPOD_NODE_OPS_CONTRACT_VERSION } from "../../tools/src/hwpod-node-ops-contract.ts";

const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;

export async function handleHwpodNodeOpsHttp(request, response, options) {
  if (request.method === "GET") {
    const planId = new URL(request.url, "http://hwlab.local").searchParams.get("planId")?.trim() ?? "";
    if (planId) {
      const operation = await options.hwpodNodeWsRegistry.lookup(planId);
      sendJson(response, operation ? 200 : 404, operation
        ? { ok: true, status: operation.result ? "completed" : "running", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, ...operation }
        : { ok: false, status: "not-found", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, planId, error: { code: "hwpod_operation_not_found", message: `hwpod operation ${planId} was not found` } });
      return;
    }
    sendJson(response, 200, {
      ok: true,
      status: "ready",
      contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
      route: "/v1/hwpod-node-ops",
      specAuthority: "code-agent-workspace",
      compiler: "hwpod-compiler-cli",
      apiRole: "node-ops-forwarder",
      nodeRole: "thin-hwpod-node-executor",
      supportedOps: Array.from(HWPOD_NODE_OPS),
      websocket: options.hwpodNodeWsRegistry.describe()
    });
    return;
  }
  if (request.method !== "POST") {
    sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "hwpod-node-ops only supports GET and POST" } });
    return;
  }

  const body = await readJsonObject(request, options.bodyLimitBytes);
  if (!body.ok) {
    sendJson(response, 400, body.error);
    return;
  }
  const validation = validateHwpodNodeOpsPlan(body.value);
  if (!validation.ok) {
    sendJson(response, 400, {
      ok: false,
      status: "rejected",
      contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
      error: validation.error
    });
    return;
  }

  const plan = validation.plan;
  const requestMeta = hwpodNodeOpsRequestMeta(request, options, "hwpod");
  try {
    const handled = await dispatchHwpodNodeOpsPlan(plan, requestMeta, options, { request });
    sendJson(response, handled.httpStatus, handled.payload);
  } catch (error) {
    const summary = error?.message ?? "hwpod-node-ops handler failed";
    recordHwpodNodeOpsBlocked(request, options, plan, requestMeta, "hwpod_node_handler_failed", summary, { dispatchMode: "handler-exception" });
    sendJson(response, 200, hwpodNodeOpsBlockedPayload(plan, requestMeta, summary, { dispatchMode: "handler-exception" }));
  }
}

export function handleHwlabNodeUpdateHttp(request, response, url, options) {
  if (request.method !== "GET") {
    sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "GET required." } });
    return;
  }
  sendJson(response, 200, buildHwlabNodeUpdatePayload(url, options.env ?? process.env));
}

export function handleHwlabNodeDownloadHttp(request, response, options) {
  if (request.method !== "GET") {
    sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "GET required." } });
    return;
  }
  const bundled = hwlabNodeBundledArtifact(options.env ?? process.env);
  if (!bundled.content) {
    sendJson(response, 404, { ok: false, error: { code: "hwlab_node_bundle_missing", message: "bundled hwlab-node.py is not available" } });
    return;
  }
  const bytes = Buffer.from(bundled.content, "utf8");
  response.writeHead(200, {
    "content-type": "text/x-python; charset=utf-8",
    "cache-control": "no-store",
    "x-hwlab-node-version": bundled.version || "unknown",
    "x-hwlab-node-sha256": bundled.sha256 || "",
    "content-length": String(bytes.length)
  });
  response.end(bytes);
}

export async function handleHwpodSpecDiscoveryHttp(request, response, url, options) {
  const probe = truthyFlag(url.searchParams.get("probe"));
  const observedAt = new Date().toISOString();
  let specs = await discoverHwpodSpecs(options);
  if (probe) {
    specs = await probeDiscoveredHwpodSpecs(specs, { request, options, observedAt });
  }
  sendJson(response, 200, hwpodSpecDiscoveryPayload(specs, { observedAt }));
}

export async function handleHwpodTopologyHttp(request, response, url, options) {
  if (request.method !== "GET") {
    sendJson(response, 405, { ok: false, error: { code: "method_not_allowed", message: "GET required." } });
    return;
  }
  const observedAt = new Date().toISOString();
  let specs = await discoverHwpodSpecs(options);
  if (truthyFlag(url.searchParams.get("probe"))) {
    specs = await probeDiscoveredHwpodSpecs(specs, { request, options, observedAt });
  }
  const payload = buildHwpodTopologyReadModel(specs, options.hwpodNodeWsRegistry.describe(), {
    observedAt,
    resource: url.searchParams.get("resource"),
    q: url.searchParams.get("q"),
    status: url.searchParams.get("status"),
    sort: url.searchParams.get("sort"),
    direction: url.searchParams.get("direction"),
    cursor: url.searchParams.get("cursor"),
    limit: parsePositiveInteger(url.searchParams.get("limit"), 50)
  });
  sendJson(response, 200, payload);
}

function runtimeEnvironment(env = process.env) {
  const value = env.HWLAB_GITOPS_PROFILE || env.HWLAB_ENVIRONMENT || ENVIRONMENT_DEV;
  return typeof value === "string" && value.trim() ? value.trim() : ENVIRONMENT_DEV;
}

function hwpodNodeOpsRequestMeta(request, options, label) {
  const httpContext = request?.hwlabHttpRequestContext ?? {};
  return {
    requestId: getHeader(request, "x-request-id") || httpContext.requestId || `req_${label}_${randomUUID()}`,
    traceId: getHeader(request, "x-trace-id") || `trc_${label}_${randomUUID()}`,
    serviceId: getHeader(request, "x-source-service-id") || CLOUD_API_SERVICE_ID,
    environment: runtimeEnvironment(options.env ?? process.env),
    otelTraceId: httpContext.traceId ?? null,
    traceparent: httpContext.traceparent ?? null,
    valuesPrinted: false
  };
}

function cleanText(value) {
  const text = String(value ?? "").trim();
  return text || "";
}

async function probeDiscoveredHwpodSpecs(specs, context) {
  const probed = [];
  for (const spec of specs) probed.push(await probeDiscoveredHwpodSpec(spec, context));
  return probed;
}

async function probeDiscoveredHwpodSpec(spec, { request, options, observedAt }) {
  if (spec.ok === false) return spec;
  const plan = hwpodSpecWorkspaceProbePlan(spec);
  const validation = validateHwpodNodeOpsPlan(plan);
  if (!validation.ok) {
    return {
      ...spec,
      availability: {
        ok: false,
        status: "invalid_probe_plan",
        checkedAt: observedAt,
        blocker: validation.error
      }
    };
  }
  const requestMeta = { ...hwpodNodeOpsRequestMeta(request, options, "hwpod_spec"), operationKind: "readiness-probe" };
  const handled = await dispatchHwpodNodeOpsPlan(validation.plan, requestMeta, options, { request });
  const payload = handled.payload;
  return {
    ...spec,
    availability: {
      ok: payload.ok === true,
      status: payload.ok === true ? "available" : "blocked",
      checkedAt: observedAt,
      probePlanId: payload.planId,
      nodeOpsRoute: "/v1/hwpod-node-ops",
      results: payload.results ?? [],
      blocker: payload.blocker ?? null,
      httpStatus: handled.httpStatus
    }
  };
}

async function dispatchHwpodNodeOpsPlan(plan, requestMeta, options, context = {}) {
  const hwpodNodeOpsUrl = normalizedHwpodNodeOpsUrl(options.env ?? process.env);
  const hwpodNodeWsRegistry = options.hwpodNodeWsRegistry;
  const hasWsNode = hwpodNodeWsRegistry?.hasNode?.(plan.nodeId);
  if (typeof options.hwpodNodeOpsHandler !== "function" && !hasWsNode && !hwpodNodeOpsUrl) {
    const summary = "no outbound WebSocket hwpod-node is connected and HWLAB_HWPOD_NODE_OPS_URL is not configured; cloud-api is only validating the hwpod-node-ops contract";
    const details = {
      dispatchMode: "none",
      websocketRegistryConfigured: Boolean(hwpodNodeWsRegistry),
      websocketConnected: false,
      directUrlConfigured: false
    };
    recordHwpodNodeOpsBlocked(context.request, options, plan, requestMeta, "hwpod_node_unavailable", summary, details);
    return {
      httpStatus: 200,
      payload: hwpodNodeOpsBlockedPayload(plan, requestMeta, summary, details)
    };
  }
  const handled = typeof options.hwpodNodeOpsHandler === "function"
    ? await options.hwpodNodeOpsHandler(plan, { request: context.request, requestMeta, env: options.env ?? process.env })
    : hasWsNode
      ? await hwpodNodeWsRegistry.dispatch(plan, requestMeta, { timeoutMs: parsePositiveInteger(options.env?.HWLAB_HWPOD_NODE_OPS_TIMEOUT_MS, 30000) })
      : await forwardHwpodNodeOpsPlan(hwpodNodeOpsUrl, plan, requestMeta, options);
  const results = Array.isArray(handled?.results) ? handled.results : [];
  const failed = results.some((item) => item?.ok === false);
  const payload = attachHwpodNodeOpsDiagnostics({
    ok: handled?.ok ?? !failed,
    status: handled?.status ?? (failed ? "failed" : "completed"),
    contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
    planId: plan.planId,
    hwpodId: plan.hwpodId,
    nodeId: plan.nodeId,
    acceptedOps: plan.ops.length,
    results,
    blocker: handled?.blocker ?? null,
    requestMeta
  }, requestMeta, { dispatchMode: hasWsNode ? "websocket" : hwpodNodeOpsUrl ? "direct-url" : "handler" });
  if (payload.ok === false && payload.status === "blocked" && payload.blocker?.code) {
    recordHwpodNodeOpsBlocked(context.request, options, plan, requestMeta, payload.blocker.code, payload.blocker.summary ?? "hwpod-node-ops blocked", payload.blocker.details ?? {});
  }
  return { httpStatus: handled?.httpStatus ?? (failed ? 409 : 200), payload };
}

async function forwardHwpodNodeOpsPlan(targetUrl, plan, requestMeta, options) {
  const target = await describeDirectHwpodNode(targetUrl, options);
  if (!target.ok || !target.nodeId) {
    return directHwpodNodeBlocked(plan, "hwpod_node_identity_unverified", "configured hwpod-node URL did not expose a verifiable nodeId", {
      requestedNodeId: plan.nodeId,
      targetUrl: redactNodeOpsUrl(targetUrl),
      dispatchMode: "direct-url",
      targetStatus: target.status ?? null,
      error: target.error ?? null
    }, requestMeta);
  }
  if (target.ok && target.nodeId && target.nodeId !== plan.nodeId) {
    return directHwpodNodeBlocked(plan, "hwpod_node_id_mismatch", `HWLAB_HWPOD_NODE_OPS_URL points to ${target.nodeId}, not requested node ${plan.nodeId}`, {
      requestedNodeId: plan.nodeId,
      targetNodeId: target.nodeId,
      targetUrl: redactNodeOpsUrl(targetUrl),
      dispatchMode: "direct-url"
    }, requestMeta);
  }
  const response = await fetch(targetUrl, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-request-id": requestMeta.requestId,
      "x-trace-id": requestMeta.traceId,
      "x-source-service-id": CLOUD_API_SERVICE_ID,
      ...(requestMeta.otelTraceId ? { "x-hwlab-otel-trace-id": requestMeta.otelTraceId } : {}),
      ...(requestMeta.traceparent ? { traceparent: requestMeta.traceparent } : {})
    },
    body: JSON.stringify(plan),
    signal: AbortSignal.timeout(parsePositiveInteger(options.env?.HWLAB_HWPOD_NODE_OPS_TIMEOUT_MS, 30000))
  });
  const body = await response.json().catch(() => null);
  if (!body || typeof body !== "object") {
    return directHwpodNodeBlocked(plan, "hwpod_node_response_invalid", `hwpod-node returned HTTP ${response.status} without a JSON object payload`, {
      targetUrl: redactNodeOpsUrl(targetUrl),
      dispatchMode: "direct-url",
      targetStatus: response.status
    }, requestMeta);
  }
  return {
    ok: body.ok,
    status: body.status,
    httpStatus: response.status,
    results: body.results,
    blocker: body.blocker ?? null
  };
}

function directHwpodNodeBlocked(plan, code, summary, details, requestMeta = {}) {
  const blocker = hwpodNodeOpsBlocker({ code, layer: "hwpod-node", retryable: true, summary, details }, requestMeta, details);
  return {
    ok: false,
    status: "blocked",
    httpStatus: 200,
    results: plan.ops.map((op) => ({ opId: op.opId, op: op.op, ok: false, status: "blocked", blocker })),
    blocker,
    otelTraceId: blocker.otelTraceId ?? null,
    diagnostic: blocker.diagnostic ?? null
  };
}

async function describeDirectHwpodNode(targetUrl, options) {
  try {
    const response = await fetch(targetUrl, {
      method: "GET",
      signal: AbortSignal.timeout(parsePositiveInteger(options.env?.HWLAB_HWPOD_NODE_OPS_TIMEOUT_MS, 30000))
    });
    const body = await response.json().catch(() => null);
    return { ok: response.ok && body && typeof body === "object", status: response.status, nodeId: safeOpaqueId(body?.nodeId), body };
  } catch (error) {
    return { ok: false, error: error instanceof Error ? error.message : String(error), nodeId: "" };
  }
}

function redactNodeOpsUrl(value) {
  try {
    const parsed = new URL(value);
    parsed.username = "";
    parsed.password = "";
    parsed.search = parsed.search ? "?redacted=1" : "";
    return parsed.toString();
  } catch {
    return "<invalid-url>";
  }
}

function normalizedHwpodNodeOpsUrl(env = process.env) {
  const direct = String(env.HWLAB_HWPOD_NODE_OPS_URL ?? "").trim();
  if (direct) return direct;
  const base = String(env.HWLAB_HWPOD_NODE_URL ?? "").trim().replace(/\/+$/u, "");
  return base ? `${base}/v1/hwpod-node-ops` : "";
}

function validateHwpodNodeOpsPlan(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return { ok: false, error: { code: "invalid_hwpod_node_ops_plan", message: "hwpod-node-ops body must be a JSON object" } };
  }
  if (value.contractVersion !== HWPOD_NODE_OPS_CONTRACT_VERSION) {
    return { ok: false, error: { code: "invalid_hwpod_node_ops_contract", message: `contractVersion must be ${HWPOD_NODE_OPS_CONTRACT_VERSION}`, actual: value.contractVersion ?? null } };
  }
  const planId = safeOpaqueId(value.planId) || `hwpod_plan_${randomUUID()}`;
  const nodeId = safeOpaqueId(value.nodeId);
  const hwpodId = safeOpaqueId(value.hwpodId);
  if (!nodeId) return { ok: false, error: { code: "invalid_hwpod_node_id", message: "nodeId is required" } };
  if (!hwpodId) return { ok: false, error: { code: "invalid_hwpod_id", message: "hwpodId is required" } };
  if (!Array.isArray(value.ops) || value.ops.length === 0) {
    return { ok: false, error: { code: "invalid_hwpod_node_ops", message: "ops must be a non-empty array" } };
  }
  const ops = [];
  for (const [index, item] of value.ops.entries()) {
    if (!item || typeof item !== "object" || Array.isArray(item)) {
      return { ok: false, error: { code: "invalid_hwpod_node_op", message: `ops[${index}] must be a JSON object` } };
    }
    const op = typeof item.op === "string" ? item.op.trim() : "";
    if (!HWPOD_NODE_OPS.has(op)) {
      return { ok: false, error: { code: "unsupported_hwpod_node_op", message: `unsupported hwpod-node op: ${op || "<empty>"}`, supportedOps: Array.from(HWPOD_NODE_OPS) } };
    }
    ops.push({
      opId: safeOpaqueId(item.opId) || `op_${index + 1}`,
      op,
      args: item.args && typeof item.args === "object" && !Array.isArray(item.args) ? item.args : {}
    });
  }
  return {
    ok: true,
    plan: {
      ...value,
      planId,
      hwpodId,
      nodeId,
      ops
    }
  };
}

function hwpodNodeOpsBlockedPayload(plan, requestMeta, summary, details = {}) {
  const blocker = hwpodNodeOpsBlocker({
    code: "hwpod_node_unavailable",
    layer: "hwpod-node",
    retryable: true,
    summary,
    details,
    userMessage: "hwpod-node 尚未接入执行面；cloud-api 已完成 hwpod-node-ops 合同校验。"
  }, requestMeta, details);
  return {
    ok: false,
    status: "blocked",
    contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION,
    planId: plan.planId,
    hwpodId: plan.hwpodId,
    nodeId: plan.nodeId,
    acceptedOps: plan.ops.length,
    results: plan.ops.map((op) => ({
      opId: op.opId,
      op: op.op,
      ok: false,
      status: "blocked",
      blocker
    })),
    blocker,
    otelTraceId: blocker.otelTraceId ?? null,
    diagnostic: blocker.diagnostic ?? null,
    requestMeta
  };
}

function hwpodNodeOpsBlocker(blocker, requestMeta = {}, details = {}) {
  const code = cleanText(blocker?.code) || "hwpod_node_ops_blocked";
  const summary = cleanText(blocker?.summary ?? blocker?.message) || "hwpod-node-ops blocked";
  const otelTraceId = cleanText(blocker?.otelTraceId ?? requestMeta?.otelTraceId) || null;
  const diagnostic = {
    code,
    rootCauseCode: code,
    summary,
    otelTraceId,
    traceLine: otelTraceId ? `OTel traceId: ${otelTraceId}` : null,
    details: details && typeof details === "object" ? details : {},
    valuesPrinted: false
  };
  const userMessage = cleanText(blocker?.userMessage);
  return {
    ...blocker,
    code,
    layer: blocker?.layer ?? "hwpod-node",
    retryable: blocker?.retryable ?? true,
    summary,
    ...(otelTraceId ? { otelTraceId } : {}),
    diagnostic,
    ...(userMessage ? { userMessage: `${userMessage}\n${diagnostic.traceLine ?? "OTel traceId: unavailable"}` } : {})
  };
}

function attachHwpodNodeOpsDiagnostics(payload, requestMeta, details = {}) {
  if (!payload || typeof payload !== "object" || payload.ok !== false) return payload;
  const topBlocker = payload.blocker ? hwpodNodeOpsBlocker(payload.blocker, requestMeta, payload.blocker.details ?? details) : null;
  const results = Array.isArray(payload.results)
    ? payload.results.map((result) => result?.blocker ? { ...result, blocker: hwpodNodeOpsBlocker(result.blocker, requestMeta, result.blocker.details ?? details) } : result)
    : payload.results;
  const diagnostic = topBlocker?.diagnostic ?? results?.find?.((result) => result?.blocker?.diagnostic)?.blocker?.diagnostic ?? null;
  return {
    ...payload,
    results,
    blocker: topBlocker,
    ...(diagnostic?.otelTraceId ? { otelTraceId: diagnostic.otelTraceId } : {}),
    ...(diagnostic ? { diagnostic } : {})
  };
}

function recordHwpodNodeOpsBlocked(request, options, plan, requestMeta, code, summary, details = {}) {
  const httpContext = request?.hwlabHttpRequestContext;
  const attributes = {
    "hwlab.hwpod.plan_id": cleanText(plan?.planId) || null,
    "hwlab.hwpod.hwpod_id": cleanText(plan?.hwpodId) || null,
    "hwlab.hwpod.node_id": cleanText(plan?.nodeId) || null,
    "hwlab.hwpod.accepted_ops": Array.isArray(plan?.ops) ? plan.ops.length : 0,
    "hwlab.hwpod.ops": Array.isArray(plan?.ops) ? plan.ops.map((op) => cleanText(op?.op)).filter(Boolean).join(",").slice(0, 240) : null,
    "hwlab.hwpod.blocker.code": code,
    "hwlab.hwpod.blocker.summary": String(summary ?? "").slice(0, 500),
    "hwlab.hwpod.dispatch_mode": cleanText(details?.dispatchMode) || "unknown",
    "hwlab.hwpod.websocket_connected": Boolean(details?.websocketConnected),
    "hwlab.hwpod.direct_url_configured": Boolean(details?.directUrlConfigured),
    "hwlab.hwpod.otel_trace_id": cleanText(requestMeta?.otelTraceId) || null,
    traceId: cleanText(requestMeta?.traceId) || null
  };
  if (httpContext) httpContext.otelAttributes = { ...(httpContext.otelAttributes ?? {}), ...attributes };
  const error = Object.assign(new Error(summary), { code });
  options.emitHttpRoutePhaseSpan?.(request, options, "hwpod-node-ops.blocked", Date.now(), Date.now(), "error", error, attributes);
}

async function readJsonObject(request, limitBytes) {
  const body = await readBody(request, limitBytes ?? DEFAULT_BODY_LIMIT_BYTES);
  try {
    const value = body ? JSON.parse(body) : {};
    if (!value || typeof value !== "object" || Array.isArray(value)) {
      return { ok: false, error: { accepted: false, error: { code: "invalid_params", message: "body must be a JSON object" } } };
    }
    return { ok: true, value };
  } catch (error) {
    return { ok: false, error: { accepted: false, error: { code: "parse_error", message: "Invalid JSON body", reason: error.message } } };
  }
}
