// Relay presence → pool wake (docs/ANTIMATTER_RUNNER.md §3, §0.2 item 3e).
//
// The mapping problem: the relay knows rooms (roomId = sha256 of the RUNNER's static key,
// minted inside the container) while this control plane knows pools (poolId = sha256 of the
// PROVISIONING DEVICE's static key) — two different identities on purpose. The bridge is
// self-registering: every host-slot heartbeat carries the runner's roomId, and the Worker
// refreshes `room:<roomId>` → poolId in KV. The record outlives the container (that's the
// point — wake exists to boot a slept container), and a runner identity rotation simply
// registers the new room on its first heartbeat.
//
// The hook itself (`POST /v1/hook/wake`, body `{roomId}`) is called by the relay's Room DO when
// a client connects to a room with no host present. It is authenticated by a dedicated shared
// secret (RUNNER_WAKE_SECRET — deliberately NOT the admin bearer: the relay holds it, and it
// grants exactly one power, waking). Unset ⇒ the hook is disabled and answers 501.

export interface KVLike {
  get(key: string): Promise<string | null>;
  put(key: string, value: string, opts?: { expirationTtl?: number }): Promise<void>;
}

/** room→pool records refresh on every heartbeat; the long TTL only reaps pools that stopped
 * heartbeating entirely (deleted / abandoned), matching the pool-credential lifecycle. */
export const ROOM_KEY_TTL = 90 * 24 * 60 * 60;

const ROOM_ID_PATTERN = /^[0-9a-f]{64}$/;

/** Register (refresh) the room→pool mapping a host heartbeat carries. Only the host slot may
 * claim a room — a session sandbox has no relay identity — and a malformed roomId is dropped
 * rather than stored (the value is attacker-adjacent: it rides an instance-token request). */
export async function registerRoomMapping(
  kv: KVLike,
  poolId: string,
  instanceId: string,
  body: Record<string, unknown> | null,
): Promise<void> {
  if (instanceId !== "host") return;
  const roomId = body?.roomId;
  if (typeof roomId !== "string" || !ROOM_ID_PATTERN.test(roomId)) return;
  await kv.put(`room:${roomId}`, poolId, { expirationTtl: ROOM_KEY_TTL });
}

/** The wake hook: authenticate the relay, resolve roomId → poolId, and forward a pool-auth
 * `/wake` (provision-by-another-name) to that pool's DO. Pure over injected deps — node:test
 * drives it with fakes. */
export async function handleWakeHook(opts: {
  kv: KVLike;
  secret: string | undefined;
  token: string | null;
  body: Record<string, unknown> | null;
  forward: (poolId: string) => Promise<Response>;
}): Promise<Response> {
  if (!opts.secret) return new Response("wake hook not configured", { status: 501 });
  if (!opts.token || opts.token !== opts.secret) return new Response("unauthorized", { status: 401 });

  const roomId = opts.body?.roomId;
  if (typeof roomId !== "string" || !ROOM_ID_PATTERN.test(roomId)) {
    return new Response("roomId required", { status: 422 });
  }
  const poolId = await opts.kv.get(`room:${roomId}`);
  if (!poolId) return new Response("unknown room", { status: 404 });
  if (await opts.kv.get(`revoked:${poolId}`)) return new Response("unauthorized: revoked", { status: 401 });
  return opts.forward(poolId);
}
