
import { createHash } from "node:crypto";

import {
  CLOUD_API_SERVICE_ID,
  createProtocolAuditEvent
} from "../audit/index.mjs";
import { ENVIRONMENT_DEV, assertProtocolRecord } from "../protocol/index.mjs";
import { HWLAB_M3_IO_CAPABILITY_LEVELS } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs";

const DEFAULT_TIMEOUT_MS = 2200;
const M3_IO_CONTROL_CONTRACT_VERSION = "m3-io-control-v1";
const M3_IO_CONTROL_ROUTE = "/v1/m3/io";
const M3_IO_CHAIN = Object.freeze({
  projectId: "prj_m3_hardware_loop",
  sourceGatewayId: "gwsimu_1",
  sourceGatewaySessionId: "gws_gwsimu_1",
  sourceResourceId: "res_boxsimu_1",
  sourceBoxId: "boxsimu_1",
  sourcePort: "DO1",
  targetGatewayId: "gwsimu_2",
  targetGatewaySessionId: "gws_gwsimu_2",
  targetResourceId: "res_boxsimu_2",
  targetBoxId: "boxsimu_2",
  targetPort: "DI1",
  patchPanelServiceId: "hwlab-patch-panel"
});
const M3_IO_BLOCKER_CODES = Object.freeze({
  gatewayUnavailable: "m3_gateway_session_unavailable",
  boxUnavailable: "m3_box_resource_unavailable",
  portDirectionInvalid: "m3_port_direction_invalid",
  wiringMissing: "m3_wiring_missing",
  patchPanelUnavailable: "m3_patch_panel_unavailable",
  dispatchFailed: "m3_gateway_dispatch_failed",
  runtimeDurableBlocked: "runtime_durable_not_green"
});

export async function requestRuntimeJson(url, { method = "GET", body, requestJson, timeoutMs = DEFAULT_TIMEOUT_MS }) {
  if (requestJson) {
    try {
      const result = await requestJson(url, { method, body, timeoutMs });
      return normalizeRuntimeJsonResponse(result);
    } catch (error) {
      return {
        ok: false,
        status: 0,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, {
      method,
      headers: body === undefined
        ? { accept: "application/json" }
        : {
            accept: "application/json",
            "content-type": "application/json"
          },
      body: body === undefined ? undefined : JSON.stringify(body),
      signal: controller.signal
    });
    const text = await response.text();
    let parsed = null;
    try {
      parsed = text ? JSON.parse(text) : null;
    } catch {
      parsed = null;
    }
    return {
      ok: response.ok,
      status: response.status,
      body: parsed,
      error: response.ok ? null : parsed?.error?.message ?? parsed?.message ?? response.statusText
    };
  } catch (error) {
    return {
      ok: false,
      status: 0,
      error: error.name === "AbortError" ? `request timed out after ${timeoutMs}ms` : error.message
    };
  } finally {
    clearTimeout(timer);
  }
}

export function normalizeRuntimeJsonResponse(result = {}) {
  if (Object.hasOwn(result, "ok") || Object.hasOwn(result, "body")) {
    return {
      ok: result.ok ?? (Number(result.status ?? 200) >= 200 && Number(result.status ?? 200) < 300),
      status: result.status ?? 200,
      body: result.body ?? result.json ?? null,
      error: result.error ?? result.body?.error?.message ?? null
    };
  }
  if (Object.hasOwn(result, "status") || Object.hasOwn(result, "json")) {
    const status = result.status ?? 200;
    return {
      ok: status >= 200 && status < 300,
      status,
      body: result.json ?? null,
      error: result.error ?? null
    };
  }
  return {
    ok: true,
    status: 200,
    body: result
  };
}

export async function persistRuntimeRegistrations({ runtimeStore, gatewayStatus, box, capability, command, meta }) {
  const gatewaySessionId = gatewayStatus.session?.gatewaySessionId ?? gatewayStatus.gatewaySessionId ?? gatewayStatus.registry?.gatewaySessionId ?? command.gatewaySessionId;
  const gatewayId = gatewayStatus.gatewayId ?? gatewayStatus.session?.gatewayId ?? gatewayStatus.registry?.gatewayId ?? command.gatewayId;
  const gatewaySession = {
    projectId: M3_IO_CHAIN.projectId,
    gatewaySessionId,
    gatewayId,
    serviceId: "hwlab-gateway-simu",
    endpoint: gatewayStatus.registry?.endpoint ?? gatewayStatus.session?.endpoint,
    status: "connected",
    labels: {
      m3ControlSurface: "true"
    }
  };
  const resource = {
    projectId: M3_IO_CHAIN.projectId,
    gatewaySessionId,
    resourceId: command.resourceId,
    boxId: box.boxId ?? command.boxId,
    resourceType: "simulator_endpoint",
    state: "available",
    metadata: {
      serviceId: "hwlab-box-simu",
      patchPanelOnlyPropagation: true,
      frontendBypassAllowed: false
    }
  };
  const writes = [];
  writes.push(await tryRuntimeWrite("gateway.session.register", () => runtimeStore?.registerGatewaySession?.(gatewaySession, meta)));
  writes.push(await tryRuntimeWrite("box.resource.register", () => runtimeStore?.registerBoxResource?.(resource, meta)));
  writes.push(await tryRuntimeWrite("box.capability.report", () => runtimeStore?.reportBoxCapabilities?.({ capability }, meta)));
  return writes;
}

export async function tryRuntimeWrite(label, write) {
  if (typeof write !== "function") {
    return {
      label,
      written: false,
      skipped: true,
      reason: "runtimeStore method unavailable"
    };
  }
  try {
    const result = await write();
    if (result === undefined) {
      return {
        label,
        written: false,
        skipped: true,
        reason: "runtimeStore method unavailable"
      };
    }
    return {
      label,
      written: true,
      result
    };
  } catch (error) {
    return {
      label,
      written: false,
      reason: error instanceof Error ? error.message : String(error),
      code: error?.code ?? null,
      data: error?.data ?? null
    };
  }
}

export async function successControlResult(input) {
  const records = await writeOutcomeRecords({
    ...input,
    outcome: "succeeded",
    reason: null
  });
  const target = controlTarget(input.command, input.operation.gatewaySessionId);
  const audit = auditState(records.auditWrite, records.runtimeAfter);
  const evidence = evidenceState(records.runtimeAfter, records);
  const readback = input.command.action === "di.read"
    ? {
        status: "succeeded",
        value: readValueFromGatewayResult(input.dispatch.body),
        resourceId: input.command.resourceId,
        port: input.command.port
      }
    : input.targetReadback ?? null;
  return {
    serviceId: CLOUD_API_SERVICE_ID,
    contractVersion: M3_IO_CONTROL_CONTRACT_VERSION,
    route: M3_IO_CONTROL_ROUTE,
    method: "POST",
    status: "succeeded",
    accepted: true,
    capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.ready,
    controlReady: true,
    action: input.action,
    chain: M3_IO_CHAIN,
    command: input.command,
    target,
    gatewayId: target.gatewayId,
    gatewaySessionId: target.gatewaySessionId,
    resourceId: target.resourceId,
    boxId: target.boxId,
    port: target.port,
    value: target.value,
    operationId: input.operation.operationId,
    traceId: input.meta.traceId,
    auditId: records.auditEvent.auditId,
    evidenceId: records.evidenceRecord.evidenceId,
    audit: {
      auditId: records.auditEvent.auditId,
      ...audit
    },
    evidence: {
      evidenceId: records.evidenceRecord.evidenceId,
      ...evidence
    },
    operationState: {
      status: "succeeded",
      reachable: true
    },
    auditState: audit,
    operation: {
      ...input.operation,
      status: "succeeded",
      completedAt: input.now,
      updatedAt: input.now
    },
    result: {
      value: input.command.action === "di.read"
        ? readValueFromGatewayResult(input.dispatch.body)
        : input.command.value,
      dispatch: gatewayResultSummary(input.gateway),
      patchPanel: input.patchPanel?.body ?? null,
      targetReadback: input.targetReadback ?? null
    },
    readback,
    controlPath: {
      status: "succeeded",
      cloudApi: true,
      gatewaySimu: true,
      boxSimu: true,
      patchPanel: input.action === "do.write",
      frontendBypass: false
    },
    auditEvent: records.auditEvent,
    evidenceRecord: records.evidenceRecord,
    evidenceState: evidence,
    durableStatus: durableStatus(records.runtimeAfter),
    trustBlocker: trustBlocker(records.runtimeAfter),
    blockerClassification: redactedBlockerClassification({
      code: records.runtimeAfter?.blocker,
      reason: records.runtimeAfter?.reason,
      runtime: records.runtimeAfter
    }),
    persistence: persistenceSummary(records.runtimeAfter, input.registration, input.operationWrite, records),
    observedAt: input.now
  };
}

export async function blockedControlResult(input) {
  const records = await writeOutcomeRecords({
    ...input,
    outcome: "failed",
    reason: input.reason
  });
  const target = controlTarget(input.command, input.operation.gatewaySessionId);
  const audit = auditState(records.auditWrite, records.runtimeAfter);
  const evidence = evidenceState(records.runtimeAfter, records);
  const readback = input.command.action === "di.read"
    ? null
    : input.targetReadback ?? null;
  return {
    serviceId: CLOUD_API_SERVICE_ID,
    contractVersion: M3_IO_CONTROL_CONTRACT_VERSION,
    route: M3_IO_CONTROL_ROUTE,
    method: "POST",
    status: "blocked",
    accepted: false,
    capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked,
    controlReady: false,
    action: input.action,
    chain: M3_IO_CHAIN,
    command: input.command,
    target,
    gatewayId: target.gatewayId,
    gatewaySessionId: target.gatewaySessionId,
    resourceId: target.resourceId,
    boxId: target.boxId,
    port: target.port,
    value: target.value,
    operationId: input.operation.operationId,
    traceId: input.meta.traceId,
    auditId: records.auditEvent.auditId,
    evidenceId: records.evidenceRecord.evidenceId,
    audit: {
      auditId: records.auditEvent.auditId,
      ...audit
    },
    evidence: {
      evidenceId: records.evidenceRecord.evidenceId,
      ...evidence
    },
    blocker: {
      code: input.code,
      message: input.reason,
      zh: input.reason
    },
    blockerClassification: redactedBlockerClassification({
      code: input.code,
      reason: input.reason,
      runtime: records.runtimeAfter
    }),
    operationState: {
      status: "blocked",
      reachable: false,
      blocker: input.code
    },
    auditState: audit,
    operation: {
      ...input.operation,
      status: "failed",
      completedAt: input.now,
      updatedAt: input.now,
      error: {
        code: input.code,
        message: input.reason
      }
    },
    result: {
      dispatch: input.gateway ? gatewayResultSummary(input.gateway) : null,
      patchPanel: input.patchPanel?.body ?? null,
      targetReadback: input.targetReadback ?? null
    },
    readback,
    controlPath: {
      status: "blocked",
      cloudApi: true,
      gatewaySimu: Boolean(input.gateway),
      boxSimu: input.code !== M3_IO_BLOCKER_CODES.gatewayUnavailable,
      patchPanel: Boolean(input.patchPanel?.ok),
      frontendBypass: false
    },
    auditEvent: records.auditEvent,
    evidenceRecord: records.evidenceRecord,
    evidenceState: evidence,
    durableStatus: durableStatus(records.runtimeAfter),
    trustBlocker: trustBlocker(records.runtimeAfter),
    persistence: persistenceSummary(records.runtimeAfter, input.registration, input.operationWrite, records),
    observedAt: input.now
  };
}

export function blockedResult({ action, command = null, meta, runtime, code, reason, httpStatus, now, details = {} }) {
  const target = controlTarget(command, command?.gatewaySessionId);
  const audit = {
    auditId: null,
    status: "not_written",
    durableStatus: durableStatus(runtime),
    reason: "operation was blocked before audit write"
  };
  const evidence = evidenceState(runtime, {
    auditWrite: { written: false, reason: "operation was blocked before audit write" },
    evidenceWrite: { written: false, reason: "operation was blocked before evidence write" }
  });
  return {
    serviceId: CLOUD_API_SERVICE_ID,
    contractVersion: M3_IO_CONTROL_CONTRACT_VERSION,
    route: M3_IO_CONTROL_ROUTE,
    method: "POST",
    status: "blocked",
    accepted: false,
    capabilityLevel: HWLAB_M3_IO_CAPABILITY_LEVELS.blocked,
    controlReady: false,
    action: action || "unknown",
    chain: M3_IO_CHAIN,
    command,
    target,
    gatewayId: target.gatewayId,
    gatewaySessionId: target.gatewaySessionId,
    resourceId: target.resourceId,
    boxId: target.boxId,
    port: target.port,
    value: target.value,
    operationId: null,
    traceId: meta.traceId,
    auditId: null,
    evidenceId: null,
    audit,
    evidence: {
      evidenceId: null,
      ...evidence
    },
    blocker: {
      code,
      message: reason,
      zh: reason
    },
    blockerClassification: redactedBlockerClassification({
      code,
      reason,
      runtime
    }),
    operationState: {
      status: "blocked",
      reachable: false,
      blocker: code
    },
    auditState: audit,
    result: {
      value: null,
      targetReadback: null
    },
    readback: null,
    controlPath: {
      status: "blocked",
      cloudApi: true,
      gatewaySimu: false,
      boxSimu: false,
      patchPanel: false,
      frontendBypass: false
    },
    evidenceState: evidence,
    durableStatus: durableStatus(runtime),
    trustBlocker: trustBlocker(runtime),
    persistence: {
      runtime,
      writes: []
    },
    observedAt: now,
    httpStatus,
    ...details
  };
}

export async function writeOutcomeRecords({
  action,
  command,
  operation,
  meta,
  runtimeStore,
  runtimeBefore,
  registration,
  operationWrite,
  tracePayload,
  outcome,
  reason,
  now
}) {
  const auditEvent = createProtocolAuditEvent({
    auditId: `aud_${operation.operationId.slice(3)}_${outcome}`,
    traceId: meta.traceId,
    actorType: "user",
    actorId: meta.actorId,
    action: action === "do.write" ? "m3.io.do.write" : "m3.io.di.read",
    targetType: "hardware_operation",
    targetId: operation.operationId,
    projectId: M3_IO_CHAIN.projectId,
    gatewaySessionId: operation.gatewaySessionId,
    operationId: operation.operationId,
    serviceId: CLOUD_API_SERVICE_ID,
    outcome,
    reason: reason ?? undefined,
    metadata: {
      route: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}->${M3_IO_CHAIN.patchPanelServiceId}->${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`,
      command,
      frontendBypass: false,
      runtimeDurableBefore: runtimeBefore?.status ?? "unknown"
    },
    occurredAt: now
  });
  const evidenceRecord = createEvidenceRecord({
    evidenceId: `evd_${operation.operationId.slice(3)}_${outcome}`,
    projectId: M3_IO_CHAIN.projectId,
    operationId: operation.operationId,
    traceId: meta.traceId,
    createdAt: now,
    payload: {
      outcome,
      reason,
      tracePayload,
      command,
      frontendBypass: false,
      patchPanelOnlyPropagation: true
    }
  });
  const auditWrite = await tryRuntimeWrite("audit.event.write", () => runtimeStore?.writeAuditEvent?.({ event: auditEvent }, meta));
  const evidenceWrite = await tryRuntimeWrite("evidence.record.write", () => runtimeStore?.writeEvidenceRecord?.({ record: evidenceRecord }, meta));
  const runtimeAfter = await safeRuntimeSummary(runtimeStore);
  return {
    auditEvent,
    evidenceRecord,
    auditWrite,
    evidenceWrite,
    runtimeAfter,
    registration,
    operationWrite
  };
}

export function createEvidenceRecord({ evidenceId, projectId, operationId, traceId, payload, createdAt }) {
  const metadata = {
    traceId,
    serviceId: CLOUD_API_SERVICE_ID,
    route: `${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort}->${M3_IO_CHAIN.patchPanelServiceId}->${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort}`,
    frontendBypass: false,
    patchPanelOnlyPropagation: true
  };
  const serialized = JSON.stringify({
    serviceId: CLOUD_API_SERVICE_ID,
    projectId,
    operationId,
    traceId,
    payload,
    metadata,
    createdAt
  });
  const record = {
    evidenceId,
    projectId,
    operationId,
    kind: "trace",
    uri: `memory://hwlab-cloud-api/${operationId}/m3-io-control.json`,
    mimeType: "application/json",
    sha256: createHash("sha256").update(serialized).digest("hex"),
    sizeBytes: Buffer.byteLength(serialized),
    serviceId: CLOUD_API_SERVICE_ID,
    environment: ENVIRONMENT_DEV,
    metadata,
    createdAt
  };
  assertProtocolRecord("evidenceRecord", record);
  return record;
}

export function evidenceState(runtime, records) {
  const durableReady = isDurableRuntimeReady(runtime);
  const recordsWritten = records.auditWrite?.written === true && records.evidenceWrite?.written === true;
  const evidenceWriteState = recordWriteState(records.evidenceWrite, runtime);
  if (durableReady && recordsWritten) {
    return {
      status: "green",
      sourceKind: "DEV-LIVE",
      durable: true,
      writeStatus: evidenceWriteState.status,
      durableStatus: durableStatus(runtime),
      reason: "operation/audit/evidence 已通过 durable runtime 写入；仍需 DEV 浏览器 E2E 才能宣称验收通过。"
    };
  }
  const blocker = runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked;
  return {
    status: "blocked",
    sourceKind: "BLOCKED",
    durable: runtime?.durable === true,
    blocker,
    writeStatus: evidenceWriteState.status,
    durableStatus: durableStatus(runtime),
    reason: `操作链路可达性与可信持久化分离：runtime durable 未 green（status=${runtime?.status ?? "unknown"}，blocker=${blocker}），不能宣称可信闭环完全 green。`
  };
}

export function auditState(auditWrite, runtime) {
  return {
    ...recordWriteState(auditWrite, runtime),
    durableStatus: durableStatus(runtime)
  };
}

export function recordWriteState(write, runtime) {
  if (write?.written === true && isDurableRuntimeReady(runtime)) {
    return {
      status: "persisted",
      durable: true,
      sourceKind: "DEV-LIVE"
    };
  }
  if (write?.written === true) {
    return {
      status: "written_non_durable",
      durable: false,
      sourceKind: "BLOCKED",
      blocker: runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked
    };
  }
  return {
    status: "not_written",
    durable: false,
    sourceKind: "BLOCKED",
    blocker: runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked,
    reason: write?.reason ?? "runtime write did not complete"
  };
}

export function durableStatus(runtime) {
  const ready = isDurableRuntimeReady(runtime);
  return {
    status: runtime?.status ?? "unknown",
    durable: runtime?.durable === true,
    ready: runtime?.ready === true,
    liveRuntimeEvidence: runtime?.liveRuntimeEvidence === true,
    blocker: ready ? null : runtime?.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked,
    redacted: true
  };
}

export function trustBlocker(runtime) {
  if (isDurableRuntimeReady(runtime)) return null;
  const durable = durableStatus(runtime);
  return {
    code: durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked,
    layer: "runtime-durable",
    zh: `runtime durable 未 green：${durable.blocker ?? M3_IO_BLOCKER_CODES.runtimeDurableBlocked}`,
    category: "runtime_durable",
    durableStatus: durable
  };
}

export function redactedBlockerClassification({ code, reason, runtime } = {}) {
  const blockerCode = code ?? runtime?.blocker ?? null;
  if (!blockerCode) {
    return null;
  }
  return {
    code: blockerCode,
    category: blockerCategory(blockerCode),
    runtimeStatus: runtime?.status ?? "unknown",
    redacted: true,
    message: reason ? redactBlockerMessage(reason) : null
  };
}

export function blockerCategory(code) {
  if (String(code).startsWith("runtime_durable")) return "runtime_durable";
  if (String(code).includes("gateway")) return "gateway_session";
  if (String(code).includes("patch_panel")) return "patch_panel";
  if (String(code).includes("wiring")) return "patch_panel_wiring";
  if (String(code).includes("box")) return "box_resource";
  if (String(code).includes("port")) return "port_direction";
  return "m3_io_control";
}

export function redactBlockerMessage(message) {
  return String(message)
    .replace(/https?:\/\/[^\s，,;]+/giu, "[redacted-url]")
    .replace(/postgres(?:ql)?:\/\/[^\s，,;]+/giu, "[redacted-db-url]");
}

export function controlTarget(command, gatewaySessionId) {
  return {
    gatewayId: command?.gatewayId ?? null,
    gatewaySessionId: gatewaySessionId ?? command?.gatewaySessionId ?? null,
    resourceId: command?.resourceId ?? null,
    boxId: command?.boxId ?? null,
    port: command?.port ?? null,
    value: Object.hasOwn(command ?? {}, "value") ? command.value : null
  };
}

export function persistenceSummary(runtime, registration, operationWrite, records) {
  return {
    runtime,
    writes: [
      ...(registration ?? []),
      operationWrite,
      records.auditWrite,
      records.evidenceWrite
    ].filter(Boolean)
  };
}

export async function safeRuntimeSummary(runtimeStore) {
  try {
    if (typeof runtimeStore?.readiness === "function") {
      return await runtimeStore.readiness();
    }
    if (typeof runtimeStore?.summary === "function") {
      return runtimeStore.summary();
    }
  } catch (error) {
    return {
      adapter: "unknown",
      durable: false,
      ready: false,
      status: "blocked",
      blocker: "runtime_summary_unavailable",
      reason: error instanceof Error ? error.message : String(error),
      liveRuntimeEvidence: false
    };
  }
  return {
    adapter: "none",
    durable: false,
    ready: false,
    status: "blocked",
    blocker: "runtime_store_missing",
    reason: "runtime store is not available",
    liveRuntimeEvidence: false
  };
}

export function isDurableRuntimeReady(runtime) {
  if (!runtime || typeof runtime !== "object") return false;
  if (runtime.durable !== true) return false;
  if (runtime.ready === false) return false;
  if (runtime.liveRuntimeEvidence !== true) return false;
  return !["blocked", "degraded", "failed"].includes(String(runtime.status ?? "").toLowerCase());
}

export function readValueFromGatewayResult(result) {
  return result?.result?.value ?? result?.value ?? null;
}

export function gatewayResultSummary(gateway) {
  return {
    gatewayId: gateway.gatewayId,
    gatewaySessionId: gateway.gatewaySessionId,
    url: redactUrl(gateway.url),
    role: gateway.role
  };
}

export function publicGatewayStatus(status) {
  return {
    gatewayId: status?.gatewayId ?? status?.session?.gatewayId ?? null,
    gatewaySessionId: status?.session?.gatewaySessionId ?? status?.gatewaySessionId ?? null,
    boxCount: Array.isArray(status?.registry?.boxes) ? status.registry.boxes.length : null
  };
}

export function finalizeControlResult(result) {
  return result;
}

export function ensureTrailingSlash(url) {
  return url.endsWith("/") ? url : `${url}/`;
}

export function trimUrl(value) {
  const text = String(value ?? "").trim();
  return text ? text : null;
}

export function parseTimeout(value) {
  const parsed = Number.parseInt(value ?? "", 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
}

export function redactUrl(url) {
  try {
    const parsed = new URL(url);
    parsed.username = parsed.username ? "***" : "";
    parsed.password = parsed.password ? "***" : "";
    return parsed.toString();
  } catch {
    return "invalid-url";
  }
}
