#!/usr/bin/env node
import { createAuditEvent, createEvidenceRecord, operationContext } from "../../internal/sim/l2-runtime.mjs";
import {
  createGatewayState,
  createHealth,
  resourceIdForBox,
  selectInstanceValue
} from "../../internal/sim/model.mjs";
import { fetchJson, createJsonServer, listen, parsePort, readJson } from "../../internal/sim/http.mjs";
import { createRpcErrorBody, ERROR_CODES } from "../../internal/protocol/index.mjs";

const gatewayId = selectInstanceValue(process.env.HWLAB_GATEWAY_ID, "gateway-a", {
  requireIndexForMultiple: true,
  label: "HWLAB_GATEWAY_ID"
});
const port = parsePort(process.env.PORT, 7101);
const endpoint = `http://127.0.0.1:${port}`;
const boxUrls = parseBoxUrls(process.env.HWLAB_BOX_URLS);
const boxes = parseBoxes({
  resources: process.env.HWLAB_BOX_RESOURCES,
  ids: process.env.HWLAB_BOX_IDS,
  urls: boxUrls,
  instanceScoped: parseList(process.env.HWLAB_GATEWAY_ID).length > 1
});
const state = createGatewayState({
  gatewayId,
  endpoint,
  boxes: boxes.map((box) => box.resourceId)
});
state.registry = {
  gatewayId,
  gatewaySessionId: state.session.gatewaySessionId,
  endpoint,
  boxes
};
state.lastHeartbeatAt = state.session.lastSeenAt;

const routes = new Map();

routes.set("GET /health/live", () =>
  ({
    ...createHealth({ serviceId: "hwlab-gateway-simu", name: gatewayId }),
    gatewayId,
    gatewaySessionId: state.session.gatewaySessionId
  })
);
routes.set("GET /status", () => state);
routes.set("GET /boxes", () => ({
  gatewayId,
  gatewaySessionId: state.session.gatewaySessionId,
  boxes: state.registry.boxes
}));
routes.set("POST /register", async ({ req }) => {
  const body = await readJson(req);
  const now = new Date().toISOString();
  const registered = normalizeBoxRegistration(body);
  upsertBox(registered);
  state.boxes = state.registry.boxes.map((box) => box.resourceId);
  state.updatedAt = now;
  state.session.lastSeenAt = now;

  return {
    accepted: true,
    serviceId: "hwlab-gateway-simu",
    gatewayId,
    gatewaySessionId: state.session.gatewaySessionId,
    registered,
    registry: state.registry
  };
});
routes.set("POST /heartbeat", async ({ req }) => {
  const body = await readJson(req);
  const now = new Date().toISOString();
  state.lastHeartbeatAt = now;
  state.session.lastSeenAt = now;
  state.updatedAt = now;

  return {
    accepted: true,
    serviceId: "hwlab-gateway-simu",
    gatewayId,
    gatewaySessionId: state.session.gatewaySessionId,
    heartbeat: {
      observedAt: now,
      boxCount: state.registry.boxes.length,
      reportedBy: body.reportedBy ?? gatewayId
    }
  };
});
routes.set("GET /capabilities", async () => capabilityReport());
routes.set("POST /capabilities/report", async () => capabilityReport());
routes.set("POST /invoke", async ({ req }) => {
  const body = await readJson(req);
  return invokeBox(body);
});

function rpcHttpError(statusCode, { code, message, data = {} }) {
  return Object.assign(new Error(message), {
    statusCode,
    body: createRpcErrorBody({
      code,
      message,
      data
    })
  });
}

function parseBoxUrls(value) {
  return String(value ?? "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function parseList(value) {
  return String(value ?? "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function parseBoxes({ resources, ids, urls, instanceScoped = false }) {
  const explicitResources = instanceScoped ? selectScopedList(resources, "HWLAB_BOX_RESOURCES") : parseList(resources);
  const explicitIds = instanceScoped ? selectScopedList(ids, "HWLAB_BOX_IDS") : parseList(ids);
  const explicitUrls = instanceScoped ? selectScopedList(urls.join(","), "HWLAB_BOX_URLS") : urls;
  const count = Math.max(explicitResources.length, explicitIds.length, explicitUrls.length, 1);
  const parsed = [];

  for (let index = 0; index < count; index += 1) {
    const boxId =
      explicitIds[index] ??
      explicitResources[index]?.replace(/^res_/, "") ??
      (count === 1 ? "boxsim_alpha" : `box-${index + 1}`);
    const resourceId = explicitResources[index] ?? resourceIdForBox(boxId);
    parsed.push({
      boxId,
      resourceId,
      url: explicitUrls[index] ?? null,
      state: "registered"
    });
  }

  return parsed;
}

function selectScopedList(value, label = "scoped list") {
  const items = parseList(value);
  if (items.length <= 1) {
    return items;
  }
  const selected = selectInstanceValue(items.join(","), "", {
    requireIndexForMultiple: true,
    label
  });
  return selected ? [selected] : [];
}

function normalizeBoxRegistration(body) {
  const boxId = body.boxId ?? body.identity?.boxId;
  const resourceId = body.resourceId ?? body.resource?.resourceId ?? (boxId ? resourceIdForBox(boxId) : null);
  if (!boxId || !resourceId) {
    throw rpcHttpError(400, {
      code: ERROR_CODES.invalidParams,
      message: "register requires boxId or resourceId",
      data: {
        reason: "identity_required"
      }
    });
  }

  return {
    boxId,
    resourceId,
    url: body.url ?? body.endpoint ?? null,
    state: "registered",
    capabilities: Array.isArray(body.capabilities) ? body.capabilities : undefined,
    registeredAt: new Date().toISOString()
  };
}

function upsertBox(box) {
  const index = state.registry.boxes.findIndex((item) => item.resourceId === box.resourceId);
  if (index >= 0) {
    state.registry.boxes[index] = {
      ...state.registry.boxes[index],
      ...box
    };
    return;
  }
  state.registry.boxes.push(box);
}

async function capabilityReport() {
  const reports = [];
  for (const box of state.registry.boxes) {
    if (box.url) {
      try {
        const response = await fetchJson(new URL("/capabilities", box.url));
        if (response.ok) {
          const body = response.body;
          const capabilities = body?.capabilities ?? [];
          upsertBox({
            ...box,
            boxId: body?.boxId ?? box.boxId,
            resourceId: body?.resourceId ?? box.resourceId,
            capabilities
          });
          reports.push({
            boxId: body?.boxId ?? box.boxId,
            resourceId: body?.resourceId ?? box.resourceId,
            url: box.url,
            status: "available",
            capabilities
          });
          continue;
        }
      } catch (error) {
        reports.push({
          boxId: box.boxId,
          resourceId: box.resourceId,
          url: box.url,
          status: "unreachable",
          capabilities: box.capabilities ?? [],
          error: error instanceof Error ? error.message : String(error)
        });
        continue;
      }
    }

    reports.push({
      boxId: box.boxId,
      resourceId: box.resourceId,
      url: box.url,
      status: box.capabilities ? "cached" : "unreachable",
      capabilities: box.capabilities ?? []
    });
  }

  state.capabilityReport = {
    serviceId: "hwlab-gateway-simu",
    gatewayId,
    gatewaySessionId: state.session.gatewaySessionId,
    reportedAt: new Date().toISOString(),
    boxes: reports
  };

  return state.capabilityReport;
}

function findBoxTarget(body) {
  const resourceId = body.resourceId;
  const boxId = body.boxId;
  return state.registry.boxes.find(
    (box) => (resourceId && box.resourceId === resourceId) || (boxId && box.boxId === boxId)
  );
}

async function invokeBox(body) {
  const box = findBoxTarget(body);
  if (!box) {
    throw rpcHttpError(404, {
      code: ERROR_CODES.sessionNotFound,
      message: "Target box is not registered with this gateway",
      data: {
        gatewayId,
        gatewaySessionId: state.session.gatewaySessionId,
        boxId: body.boxId ?? null,
        resourceId: body.resourceId ?? null
      }
    });
  }
  if (!box.url) {
    throw rpcHttpError(409, {
      code: ERROR_CODES.operationRejected,
      message: "Target box has no invoke URL",
      data: {
        gatewayId,
        resourceId: box.resourceId,
        reason: "box_url_missing"
      }
    });
  }

  const context = operationContext(body, {
    serviceId: "hwlab-gateway-simu",
    actorId: "svc_hwlab-gateway-simu"
  });
  const forwarded = {
    ...body,
    operationId: context.operationId,
    traceId: context.traceId,
    actorType: "service",
    actorId: "svc_hwlab-gateway-simu",
    source: body.source ?? "gateway-simu"
  };
  const response = await fetchJson(new URL("/invoke", box.url), {
    method: "POST",
    body: forwarded
  });

  if (!response.ok) {
    throw Object.assign(new Error("box invoke failed"), {
      statusCode: response.status,
      body:
        response.body ??
        createRpcErrorBody({
          code: ERROR_CODES.hwlabUnknown,
          message: "Box invoke failed",
          data: {
            status: response.status
          }
        })
    });
  }

  const now = new Date().toISOString();
  const audit = createAuditEvent({
    serviceId: "hwlab-gateway-simu",
    action: "hardware.operation.dispatched",
    targetType: "box_resource",
    targetId: box.resourceId,
    projectId: state.projectId,
    gatewaySessionId: state.session.gatewaySessionId,
    operationId: context.operationId,
    traceId: context.traceId,
    actorType: "service",
    actorId: "svc_hwlab-gateway-simu",
    outcome: "succeeded",
    metadata: {
      gatewayId,
      boxId: box.boxId,
      resourceId: box.resourceId,
      capabilityId: body.capabilityId ?? null,
      port: body.port ?? null,
      forwardedTo: box.url
    },
    occurredAt: now
  });
  const evidence = createEvidenceRecord({
    serviceId: "hwlab-gateway-simu",
    projectId: state.projectId,
    operationId: context.operationId,
    traceId: context.traceId,
    kind: "trace",
    payload: {
      gatewayId,
      boxId: box.boxId,
      resourceId: box.resourceId,
      result: response.body
    },
    metadata: {
      gatewaySessionId: state.session.gatewaySessionId,
      forwardedTo: box.url,
      producerServiceId: "hwlab-gateway-simu"
    },
    createdAt: now
  });

  state.lastDispatch = {
    operationId: context.operationId,
    traceId: context.traceId,
    resourceId: box.resourceId,
    observedAt: now,
    accepted: true
  };
  state.updatedAt = now;

  return {
    accepted: true,
    serviceId: "hwlab-gateway-simu",
    gatewayId,
    gatewaySessionId: state.session.gatewaySessionId,
    operationId: context.operationId,
    traceId: context.traceId,
    target: {
      boxId: box.boxId,
      resourceId: box.resourceId,
      url: box.url
    },
    result: response.body,
    auditId: audit.auditId,
    evidenceId: evidence.evidenceId,
    audit,
    evidence
  };
}

listen(createJsonServer({ routes }), port);
