import { createHash, createHmac, timingSafeEqual } from "node:crypto";
import { createReadStream, createWriteStream } from "node:fs";
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { createServer } from "node:http";
import { dirname, join, resolve } from "node:path";
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
import { fileURLToPath } from "node:url";

import {
  MAP_REFERENCE_SEARCH_SCHEMA,
  TRANSPORT_STATION_PROFILE_ID,
  createReferenceStationSource,
} from "./reference-station-source.mjs";
import { loadZoneSourceProfile } from "./zone-source-snapshot.mjs";

const canonicalCesiumAssetIds = ["1", "2", "96188"];
const cesiumVerificationTransportVersion = 4;
// The neighbouring VPN egress only serves official Cesium/Bing traffic for
// Map Gateway.  Other upstreams retain their existing direct policy.
const canonicalCesiumEgressHosts = new Set([
  "api.cesium.com",
  "assets.ion.cesium.com",
  "dev.virtualearth.net",
  "ecn.t0.tiles.virtualearth.net",
  "ecn.t1.tiles.virtualearth.net",
  "ecn.t2.tiles.virtualearth.net",
  "ecn.t3.tiles.virtualearth.net",
]);
const config = await readConfig();
const zoneSourceProfileId = String(process.env.ZONE_SOURCE_PROFILE_ID || "moscow-pmd-slow-zones").trim();
const zoneSourceRoot = resolve(String(
  process.env.ZONE_SOURCE_SNAPSHOT_ROOT
    || resolve(dirname(fileURLToPath(import.meta.url)), "../zone-sources"),
).trim());
// The route contract is stable while the provider behind this profile is
// replaceable: today it is the audited MMap snapshot, later an API adapter can
// atomically materialise the same manifest + generation envelope.
const zoneSource = await loadZoneSourceProfile(zoneSourceRoot, zoneSourceProfileId);
const referenceStationSource = await createReferenceStationSource({
  seedFile: config.referenceStationSeedFile,
  cacheDir: config.cacheDir,
  fetchEnabled: config.referenceStationFetchEnabled,
  overpassApiBase: config.referenceStationOverpassUrl,
  cellDegrees: config.referenceStationCellDegrees,
  timeoutMs: config.upstreamTimeoutMs,
});
const liveCache = createCacheStore("live", config.cacheDir, true);
const offlineSnapshot = config.offlineSnapshotDir ? createCacheStore("offline-snapshot", config.offlineSnapshotDir, false) : null;
const inflightWrites = new Map();
const gatewayDiagnostics = createGatewayDiagnostics(config.slowUpstreamMs);
// Provider endpoint credentials are kept only here or in the private volume.
// They are never part of an endpoint response or a browser resource URL.
const activeIonEndpoints = new Map();
const inflightIonEndpointRefreshes = new Map();
const credentialQueryKeys = new Set(["access_token", "accesstoken", "iontoken", "token", "key", "apikey", "api_key", "signature", "sig"]);

await initialiseCacheStore(liveCache);
if (offlineSnapshot) await initialiseCacheStore(offlineSnapshot);
// A Platform administrator may rotate the master token after deployment. The
// private NAS file wins over the legacy environment bootstrap on restart.
let activeCesiumIonToken = (await readStoredCesiumIonToken()) || config.bootstrapCesiumIonToken;
let ionTokenWriteQueue = Promise.resolve();
let ionTokenGeneration = 0;

const server = createServer(async (request, response) => {
  applyCors(request, response);
  if (request.method === "OPTIONS") return response.writeHead(204).end();
  let requestPath = "unknown";

  try {
    const requestUrl = new URL(request.url || "/", `http://${request.headers.host || "127.0.0.1"}`);
    requestPath = requestUrl.pathname;
    if (requestUrl.pathname === "/api/map/admin/cesium-ion" && ["GET", "PUT"].includes(request.method || "")) {
      return await serveCesiumIonAdmin(request, response, requestUrl.pathname);
    }
    if (requestUrl.pathname === `/internal/zone-sources/v1/profiles/${encodeURIComponent(zoneSourceProfileId)}/current`) {
      if (!["GET", "HEAD"].includes(request.method || "")) {
        response.setHeader("Allow", "GET, HEAD");
        return writeJson(response, 405, { ok: false, error: "zone_source_method_not_allowed" });
      }
      return serveZoneSourceGeneration(request, response);
    }
    if (!isAllowedRequest(request)) return writeJson(response, 401, { ok: false, error: "map_gateway_auth_required" });

    if (requestUrl.pathname === `/api/map/reference-sources/v1/profiles/${TRANSPORT_STATION_PROFILE_ID}/current`
      && ["GET", "HEAD"].includes(request.method || "")) {
      return await serveReferenceStationSnapshot(request, response, requestUrl);
    }
    if (requestUrl.pathname === `/api/map/reference-sources/v1/profiles/${TRANSPORT_STATION_PROFILE_ID}/search`
      && ["GET", "HEAD"].includes(request.method || "")) {
      return await serveReferenceStationSearch(request, response, requestUrl);
    }

    if (requestUrl.pathname === "/healthz" && request.method === "GET") {
      return writeJson(response, 200, {
        ok: true,
        service: "nodedc-map-gateway",
        cache: await cacheStats(liveCache),
        offlineSnapshot: offlineSnapshot ? await cacheStats(offlineSnapshot) : null,
        diagnostics: gatewayDiagnostics.status(),
        ionConfigured: Boolean(activeCesiumIonToken),
        assetAllowlist: [...config.assetAllowlist].map(Number).sort((left, right) => left - right),
        anonymousAccess: config.allowAnonymous,
        zoneSource: {
          profileId: zoneSourceProfileId,
          authorityId: zoneSource.generation.authorityId,
          datasetId: zoneSource.generation.datasetId,
          sourceRevision: zoneSource.generation.sourceRevision,
          contentDigest: zoneSource.generation.contentDigest,
          sourceZoneCount: zoneSource.generation.metadata.sourceZoneCount,
          publishedZoneCount: zoneSource.generation.metadata.publishedZoneCount,
        },
        referenceSources: {
          transportStations: await referenceStationSource.status(),
        },
      });
    }

    const ionAssetMatch = requestUrl.pathname.match(/^\/api\/map\/ion\/assets\/(\d+)\/endpoint$/);
    if (ionAssetMatch && request.method === "GET") {
      return await serveIonEndpoint(request, response, ionAssetMatch[1]);
    }

    if (requestUrl.pathname === "/api/map/cache" && ["GET", "HEAD"].includes(request.method || "")) {
      return await serveCachedUpstream(request, response, requestUrl);
    }

    return writeJson(response, 404, { ok: false, error: "map_gateway_route_not_found" });
  } catch (error) {
    // A client can cancel a streamed cache response after its headers have
    // already been sent. Do not try to turn that into JSON: Node would throw
    // ERR_HTTP_HEADERS_SENT and take the entire gateway process down.
    if (response.headersSent || response.writableEnded) {
      if (!response.writableEnded) response.destroy();
      return;
    }
    const status = Number(error?.statusCode || 500);
    const errorCode = safeDiagnosticCode(error?.message || "map_gateway_error");
    gatewayDiagnostics.recordGatewayFailure(errorCode);
    console.warn(JSON.stringify({ event: "map_gateway_request_failed", route: requestPath, status, error: errorCode }));
    return writeJson(response, Number.isInteger(status) && status >= 400 && status < 600 ? status : 500, {
      ok: false,
      error: errorCode,
    });
  }
});

server.listen(config.port, "0.0.0.0", () => {
  console.log(`NODE.DC Map Gateway listening on http://0.0.0.0:${config.port}`);
  console.log(`Live map cache: ${config.cacheDir} (${config.mode}, max ${Math.round(config.maxCacheBytes / 1024 / 1024)} MB)`);
  if (offlineSnapshot) console.log(`Offline map snapshot: ${config.offlineSnapshotDir} (read-only)`);
  console.log(`Zone source: ${zoneSourceProfileId}@${zoneSource.generation.sourceRevision} (${zoneSource.generation.metadata.publishedZoneCount} zones)`);
  console.log(`Map reference source: ${TRANSPORT_STATION_PROFILE_ID}`);
});

process.on("SIGTERM", () => server.close());
process.on("SIGINT", () => server.close());

function serveZoneSourceGeneration(request, response) {
  if (request.headers["if-none-match"] === zoneSource.etag) {
    response.writeHead(304, { ETag: zoneSource.etag, "Cache-Control": "private, max-age=60" });
    return response.end();
  }
  response.writeHead(200, {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": zoneSource.body.byteLength,
    "Cache-Control": "private, max-age=60",
    ETag: zoneSource.etag,
    "X-Content-Type-Options": "nosniff",
  });
  return response.end(request.method === "HEAD" ? undefined : zoneSource.body);
}

async function serveReferenceStationSnapshot(request, response, requestUrl) {
  const rawBbox = requestUrl.searchParams.get("bbox");
  const bbox = rawBbox ? rawBbox.split(",").map(Number) : undefined;
  const snapshot = await referenceStationSource.snapshot({ bbox });
  const etag = `"sha256:${snapshot.contentDigest}"`;
  if (request.headers["if-none-match"] === etag) {
    response.writeHead(304, { ETag: etag, "Cache-Control": "private, max-age=300" });
    return response.end();
  }
  const body = Buffer.from(`${JSON.stringify(snapshot)}\n`);
  response.writeHead(200, {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": body.byteLength,
    "Cache-Control": "private, max-age=300",
    ETag: etag,
    "X-Content-Type-Options": "nosniff",
  });
  return response.end(request.method === "HEAD" ? undefined : body);
}

async function serveReferenceStationSearch(request, response, requestUrl) {
  const search = await referenceStationSource.search({
    query: requestUrl.searchParams.get("q"),
    limit: requestUrl.searchParams.get("limit"),
  });
  if (search.schemaVersion !== MAP_REFERENCE_SEARCH_SCHEMA) {
    throw new Error("reference_station_search_contract_invalid");
  }
  const etag = `"sha256:${search.contentDigest}"`;
  if (request.headers["if-none-match"] === etag) {
    response.writeHead(304, { ETag: etag, "Cache-Control": "private, max-age=300" });
    return response.end();
  }
  const body = Buffer.from(`${JSON.stringify(search)}\n`);
  response.writeHead(200, {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": body.byteLength,
    "Cache-Control": "private, max-age=300",
    ETag: etag,
    "X-Content-Type-Options": "nosniff",
  });
  return response.end(request.method === "HEAD" ? undefined : body);
}

async function readConfig() {
  const mode = String(process.env.MAP_CACHE_MODE || "readwrite").trim().toLowerCase();
  if (!["readwrite", "readonly", "offline"].includes(mode)) throw new Error("invalid_map_cache_mode");
  const allowlist = parseList(process.env.MAP_GATEWAY_UPSTREAM_ALLOWLIST || "api.cesium.com,assets.ion.cesium.com,tile.openstreetmap.org,dev.virtualearth.net,ecn.t0.tiles.virtualearth.net,ecn.t1.tiles.virtualearth.net,ecn.t2.tiles.virtualearth.net,ecn.t3.tiles.virtualearth.net");
  const mapEgress = await readMapEgressConfig();
  const ionEndpointTtlMs = parsePositiveInt(process.env.CESIUM_ION_ENDPOINT_TTL_SECONDS, 300) * 1000;
  const configuredRefreshAheadMs = parsePositiveInt(process.env.CESIUM_ION_ENDPOINT_REFRESH_AHEAD_SECONDS, 60) * 1000;
  return {
    port: parsePositiveInt(process.env.PORT, 18103),
    cacheDir: String(process.env.MAP_CACHE_DIR || "/var/lib/nodedc-map-cache").trim(),
    offlineSnapshotDir: String(process.env.MAP_OFFLINE_SNAPSHOT_DIR || "").trim(),
    mode,
    maxCacheBytes: parsePositiveInt(process.env.MAP_CACHE_MAX_MB, 20480) * 1024 * 1024,
    maxObjectBytes: parsePositiveInt(process.env.MAP_CACHE_MAX_OBJECT_MB, 128) * 1024 * 1024,
    defaultTtlMs: parsePositiveInt(process.env.MAP_CACHE_DEFAULT_TTL_SECONDS, 604800) * 1000,
    upstreamTimeoutMs: parsePositiveInt(process.env.MAP_GATEWAY_UPSTREAM_TIMEOUT_SECONDS, 30) * 1000,
    slowUpstreamMs: parsePositiveInt(process.env.MAP_GATEWAY_SLOW_UPSTREAM_SECONDS, 2) * 1000,
    ionEndpointTtlMs,
    ionEndpointRefreshAheadMs: Math.min(configuredRefreshAheadMs, Math.max(0, ionEndpointTtlMs - 1000)),
    // Test-only loopback escape hatch for the cache policy smoke test. It is
    // impossible to enable in production and never broadens the real HTTPS
    // allowlist.
    testAllowHttpLoopback: process.env.NODE_ENV === "test" && parseBoolean(process.env.MAP_GATEWAY_TEST_ALLOW_HTTP_LOOPBACK, false),
    // Transitional bootstrap only. A token written through the Foundry admin
    // flow is held in the private cache volume and takes precedence.
    bootstrapCesiumIonToken: String(process.env.CESIUM_ION_TOKEN || "").trim(),
    cesiumIonApiBase: normalizeCesiumIonApiBase(process.env.CESIUM_ION_API_BASE_URL),
    cesiumIonTokenFile: String(process.env.MAP_GATEWAY_CESIUM_ION_TOKEN_FILE || join(String(process.env.MAP_CACHE_DIR || "/var/lib/nodedc-map-cache").trim(), "secrets", "cesium-ion-token")).trim(),
    // The production value is read from a runner-owned, read-only file. Local
    // test/dev may still inject a process-only value without creating a file.
    mapGatewayAdminSecret: await readMapGatewayAdminSecret(),
    mapEgress,
    cesiumEgressHosts: canonicalCesiumEgressHosts,
    // The Foundry Map Page contract always needs terrain (1), imagery (2),
    // and the canonical buildings asset. A malformed legacy environment value
    // must never turn all three into 403 before the Gateway can reach Cesium.
    // Root may add further approved assets, but cannot remove these core IDs.
    assetAllowlist: new Set([...canonicalCesiumAssetIds, ...parseList(process.env.CESIUM_ION_ASSET_ALLOWLIST)]),
    upstreamAllowlist: new Set(allowlist),
    legacyCacheHosts: new Set(parseList(process.env.MAP_GATEWAY_LEGACY_CACHE_HOSTS || "")),
    offlineProviderAllowlist: new Set(parseList(process.env.MAP_GATEWAY_OFFLINE_PROVIDER_ALLOWLIST || "")),
    referenceStationSeedFile: String(
      process.env.MAP_REFERENCE_STATION_SEED_FILE
      || resolve(dirname(fileURLToPath(import.meta.url)), "../reference-sources/transport-stations/moscow-v1.json"),
    ).trim(),
    referenceStationFetchEnabled: parseBoolean(process.env.MAP_REFERENCE_STATION_FETCH_ENABLED, true),
    referenceStationCellDegrees: Number(process.env.MAP_REFERENCE_STATION_CELL_DEGREES || 0.5),
    referenceStationOverpassUrl: String(process.env.MAP_REFERENCE_STATION_OVERPASS_URL || "https://overpass-api.de/api/interpreter").trim(),
    corsOrigins: new Set(parseList(process.env.MAP_GATEWAY_CORS_ORIGIN || "http://127.0.0.1:3333,http://localhost:3333")),
    allowAnonymous: parseBoolean(process.env.MAP_GATEWAY_ALLOW_ANONYMOUS, process.env.NODE_ENV !== "production"),
    trustedSubjectHeader: String(process.env.MAP_GATEWAY_TRUSTED_SUBJECT_HEADER || "x-nodedc-user-id").toLowerCase(),
  };
}

async function readMapEgressConfig() {
  const rawUrl = String(process.env.MAP_GATEWAY_EGRESS_URL || "").trim();
  if (!rawUrl) return { url: "", proxyToken: "" };
  let url;
  try {
    url = new URL(rawUrl);
  } catch {
    throw new Error("map_gateway_egress_url_invalid");
  }
  if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
    throw new Error("map_gateway_egress_url_invalid");
  }
  const tokenFile = String(process.env.MAP_GATEWAY_EGRESS_PROXY_TOKEN_FILE || "").trim();
  if (!tokenFile) throw new Error("map_gateway_egress_proxy_token_file_required");
  let proxyToken;
  try {
    proxyToken = (await readFile(tokenFile, "utf8")).trim();
  } catch {
    throw new Error("map_gateway_egress_proxy_token_file_unreadable");
  }
  if (!proxyToken || proxyToken.length > 4096 || /[\u0000-\u001f\u007f\s]/.test(proxyToken)) {
    throw new Error("map_gateway_egress_proxy_token_file_invalid");
  }
  return { url: url.toString(), proxyToken };
}

async function readMapGatewayAdminSecret() {
  const secretFile = String(process.env.NODEDC_MAP_GATEWAY_ADMIN_SECRET_FILE || "").trim();
  if (secretFile) {
    let secret;
    try {
      secret = (await readFile(secretFile, "utf8")).trim();
    } catch {
      throw new Error("map_gateway_admin_secret_file_unreadable");
    }
    if (!/^[A-Za-z0-9_-]{48,256}$/.test(secret)) throw new Error("map_gateway_admin_secret_file_invalid");
    return secret;
  }
  if (process.env.NODE_ENV === "production") throw new Error("map_gateway_admin_secret_file_required");
  return String(process.env.NODEDC_MAP_GATEWAY_ADMIN_SECRET || "").trim();
}

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

function parsePositiveInt(value, fallback) {
  const parsed = Number.parseInt(String(value || ""), 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

function parseBoolean(value, fallback) {
  if (value === undefined || value === "") return fallback;
  return ["1", "true", "yes", "on"].includes(String(value).trim().toLowerCase());
}

function normalizeCesiumIonApiBase(value) {
  const configured = String(value || "https://api.cesium.com/").trim();
  let base;
  try { base = new URL(configured); } catch { throw new Error("invalid_cesium_ion_api_base"); }
  const testHttp = process.env.NODE_ENV === "test" && base.protocol === "http:";
  if ((!testHttp && base.protocol !== "https:") || base.username || base.password || base.search || base.hash) {
    throw new Error("invalid_cesium_ion_api_base");
  }
  base.pathname = base.pathname.replace(/\/+$/, "") || "/";
  return base.toString();
}

function normalizeIonReferer(value) {
  const raw = String(value || "").trim();
  if (!raw || raw.length > 2048) return "";
  let referer;
  try { referer = new URL(raw); } catch { return ""; }
  if (!['http:', 'https:'].includes(referer.protocol) || referer.username || referer.password) return "";
  referer.search = "";
  referer.hash = "";
  return referer.toString();
}

function requestIonReferer(request) {
  // This is an internal Foundry-to-Gateway transport header. It is normalized
  // before reaching Cesium and, for admin calls, included in the HMAC payload.
  return normalizeIonReferer(request.headers['x-nodedc-ion-referer']);
}

function ionRefererHeaders(ionReferer) {
  return ionReferer ? { referer: ionReferer } : {};
}

function cesiumIonAssetEndpointUrl(assetId) {
  return new URL(`v1/assets/${encodeURIComponent(assetId)}/endpoint`, config.cesiumIonApiBase).toString();
}

function applyCors(request, response) {
  const origin = String(request.headers.origin || "");
  if (!origin || (!config.corsOrigins.has("*") && !config.corsOrigins.has(origin))) return;
  response.setHeader("access-control-allow-origin", config.corsOrigins.has("*") ? "*" : origin);
  response.setHeader("access-control-allow-methods", "GET, HEAD, OPTIONS");
  response.setHeader("access-control-allow-headers", "Range, Content-Type");
  response.setHeader("vary", "Origin");
}

async function serveCesiumIonAdmin(request, response, pathname) {
  const rawBody = request.method === "PUT" ? await readSmallBody(request) : "";
  const { actorId, ionReferer } = verifyAdminRequest(request, pathname, rawBody);
  if (request.method === "GET") {
    const metadata = await readCesiumIonMetadata();
    // A Bing asset endpoint request starts a billable provider session. The
    // normal GET path must therefore report the persisted verification state,
    // rather than re-validating every time an administrator opens the modal.
    // Artifacts created before this check existed have no state: validate that
    // one existing private value exactly once and then remember the safe result.
    let verification = metadata?.verification || (activeCesiumIonToken ? null : "not-configured");
    // Transport v4 adds production-shaped file endpoint scopes and safe
    // cache-first refresh. Recheck a persisted failure once so a false v3
    // result cannot survive the artifact upgrade forever.
    const mustUpgradeLegacyFailure = metadata?.verification === "failed"
      && metadata.verificationTransportVersion !== cesiumVerificationTransportVersion;
    if ((!verification || mustUpgradeLegacyFailure) && activeCesiumIonToken) {
      verification = (await verifyCesiumIonToken(activeCesiumIonToken, ionReferer)).verification;
      if (metadata) await persistCesiumIonVerification(metadata, verification);
    }
    return writeJson(response, 200, {
      ok: true,
      configured: Boolean(activeCesiumIonToken),
      updatedAt: metadata?.updatedAt || null,
      updatedBy: metadata?.updatedBy || null,
      verification: verification || "failed",
    });
  }

  let input;
  try {
    input = JSON.parse(rawBody);
  } catch {
    throw gatewayError("invalid_cesium_ion_token", 400);
  }
  const token = typeof input?.token === "string" ? input.token.trim() : "";
  if (token.length < 16 || token.length > 4096 || /[\u0000-\u001f\u007f\s]/.test(token)) {
    throw gatewayError("invalid_cesium_ion_token", 400);
  }
  const verification = await verifyCesiumIonToken(token, ionReferer);
  if (verification.verification !== "verified") throw gatewayError("cesium_ion_token_verification_failed", 422);
  const metadata = await persistCesiumIonToken(token, actorId, verification);
  return writeJson(response, 200, {
    ok: true,
    configured: true,
    updatedAt: metadata.updatedAt,
    updatedBy: metadata.updatedBy,
    verification: verification.verification,
  });
}

async function readSmallBody(request) {
  const chunks = [];
  let size = 0;
  for await (const chunk of request) {
    size += chunk.length;
    if (size > 8 * 1024) throw gatewayError("map_gateway_admin_payload_too_large", 413);
    chunks.push(chunk);
  }
  return Buffer.concat(chunks).toString("utf8");
}

function verifyAdminRequest(request, pathname, body) {
  if (!config.mapGatewayAdminSecret) throw gatewayError("map_gateway_admin_not_configured", 503);
  const timestamp = String(request.headers["x-nodedc-admin-timestamp"] || "");
  const actorId = String(request.headers["x-nodedc-admin-actor"] || "").trim();
  const suppliedSignature = String(request.headers["x-nodedc-admin-signature"] || "").trim().toLowerCase();
  const ionReferer = normalizeIonReferer(request.headers["x-nodedc-ion-referer"]);
  const timestampMs = Number(timestamp);
  if (!Number.isInteger(timestampMs) || Math.abs(Date.now() - timestampMs) > 60_000 || !/^[A-Za-z0-9._:@-]{1,256}$/.test(actorId) || !/^[a-f0-9]{64}$/.test(suppliedSignature)) {
    throw gatewayError("map_gateway_admin_unauthorized", 401);
  }
  const bodyHash = createHash("sha256").update(body).digest("hex");
  const message = `nodedc.map-gateway.admin.v2\n${request.method}\n${pathname}\n${timestamp}\n${actorId}\n${bodyHash}\n${ionReferer}`;
  const expectedSignature = createHmac("sha256", config.mapGatewayAdminSecret).update(message).digest("hex");
  const expected = Buffer.from(expectedSignature, "hex");
  const supplied = Buffer.from(suppliedSignature, "hex");
  if (expected.length !== supplied.length || !timingSafeEqual(expected, supplied)) throw gatewayError("map_gateway_admin_unauthorized", 401);
  return { actorId, ionReferer };
}

function cesiumIonMetadataFile() {
  return `${config.cesiumIonTokenFile}.metadata.json`;
}

async function readStoredCesiumIonToken() {
  try {
    return String(await readFile(config.cesiumIonTokenFile, "utf8")).trim();
  } catch (error) {
    if (error?.code !== "ENOENT") console.warn("Cesium Ion token file ignored: unreadable");
    return "";
  }
}

async function readCesiumIonMetadata() {
  try {
    const metadata = JSON.parse(await readFile(cesiumIonMetadataFile(), "utf8"));
    const updatedAt = typeof metadata?.updatedAt === "string" ? metadata.updatedAt : null;
    const updatedBy = typeof metadata?.updatedBy === "string" ? metadata.updatedBy : null;
    const verification = metadata?.verification === "verified" || metadata?.verification === "failed" ? metadata.verification : null;
    const verificationTransportVersion = Number.isInteger(metadata?.verificationTransportVersion)
      ? metadata.verificationTransportVersion
      : 0;
    return updatedAt && updatedBy ? { updatedAt, updatedBy, verification, verificationTransportVersion } : null;
  } catch (error) {
    if (error?.code !== "ENOENT") console.warn("Cesium Ion token metadata ignored: invalid file");
    return null;
  }
}

async function verifyCesiumIonToken(token, ionReferer = "") {
  // Settings must validate the complete Foundry Map Page contract. Accepting
  // a token that reads terrain and imagery but not canonical 3D Buildings
  // would defer a predictable provider 403 to the browser.
  const results = await Promise.all(canonicalCesiumAssetIds.map(async (assetId) => {
    try {
      await fetchCesiumIonEndpoint(assetId, token, ionReferer);
      return true;
    } catch {
      return false;
    }
  }));
  return { verification: results.every(Boolean) ? "verified" : "failed" };
}

async function fetchCesiumIonEndpoint(assetId, token, ionReferer = "") {
  const endpoint = await fetchWithTimeout(cesiumIonAssetEndpointUrl(assetId), {
    headers: { authorization: `Bearer ${token}`, ...ionRefererHeaders(ionReferer) },
  }, { operation: "ion-endpoint", assetId });
  if (!endpoint.ok) throw gatewayError("cesium_ion_endpoint_unavailable", endpoint.status || 502);
  const body = await endpoint.json();
  const next = body.type === "IMAGERY" && body.externalType === "BING"
    ? {
        assetId,
        type: body.type,
        externalType: "BING",
        options: {
          url: validateUpstream(String(body.options?.url || "")).toString(),
          key: String(body.options?.key || ""),
          mapStyle: String(body.options?.mapStyle || "Aerial"),
        },
        attributions: Array.isArray(body.attributions) ? body.attributions : [],
        savedAt: Date.now(),
      }
    : {
        assetId,
        type: body.type,
        url: validateUpstream(String(body.url || "")).toString(),
        accessToken: String(body.accessToken || ""),
        attributions: Array.isArray(body.attributions) ? body.attributions : [],
        savedAt: Date.now(),
      };
  if (assetId === "1" && next.type !== "TERRAIN") throw gatewayError("cesium_ion_endpoint_invalid", 502);
  if (assetId === "2" && (next.externalType !== "BING" || next.type !== "IMAGERY")) throw gatewayError("cesium_ion_endpoint_invalid", 502);
  if (!(next.externalType === "BING" ? next.options?.key : next.accessToken)) throw gatewayError("cesium_ion_endpoint_invalid", 502);
  const credentialExpiresAt = ionEndpointCredentialExpiry(next);
  if (credentialExpiresAt !== null && credentialExpiresAt <= Date.now()) throw gatewayError("cesium_ion_endpoint_expired", 502);
  return next;
}

function persistCesiumIonToken(token, actorId, verification) {
  const task = ionTokenWriteQueue.then(async () => {
    const secretDir = dirname(config.cesiumIonTokenFile);
    await mkdir(secretDir, { recursive: true, mode: 0o700 });
    await chmod(secretDir, 0o700);
    const tokenTemp = `${config.cesiumIonTokenFile}.${process.pid}.${Date.now()}.tmp`;
    const metadata = {
      updatedAt: new Date().toISOString(),
      updatedBy: actorId,
      verification: verification?.verification === "verified" ? "verified" : "failed",
      verificationTransportVersion: cesiumVerificationTransportVersion,
    };
    await writeFile(tokenTemp, `${token}\n`, { encoding: "utf8", mode: 0o600 });
    await rename(tokenTemp, config.cesiumIonTokenFile);
    await chmod(config.cesiumIonTokenFile, 0o600);
    await writeCesiumIonMetadata(metadata);
    // Asset-scoped endpoint credentials are derived from the master token.
    // Discard the prior private endpoint cache on rotation so a fresh map load
    // cannot keep using credentials minted under the old provider token.
    ionTokenGeneration += 1;
    activeCesiumIonToken = token;
    activeIonEndpoints.clear();
    inflightIonEndpointRefreshes.clear();
    await Promise.all([...config.assetAllowlist].map((assetId) => rm(ionEndpointPath(assetId), { force: true })));
    return metadata;
  });
  ionTokenWriteQueue = task.then(() => undefined, () => undefined);
  return task;
}

function persistCesiumIonVerification(expectedMetadata, verification) {
  const task = ionTokenWriteQueue.then(async () => {
    const current = await readCesiumIonMetadata();
    // Do not let an old GET validation race a newer token rotation.
    if (!current || current.updatedAt !== expectedMetadata.updatedAt || current.updatedBy !== expectedMetadata.updatedBy) return;
    await writeCesiumIonMetadata({ ...current, verification, verificationTransportVersion: cesiumVerificationTransportVersion });
  });
  ionTokenWriteQueue = task.then(() => undefined, () => undefined);
  return task;
}

async function writeCesiumIonMetadata(metadata) {
  const metadataPath = cesiumIonMetadataFile();
  const metadataTemp = `${metadataPath}.${process.pid}.${Date.now()}.tmp`;
  await writeFile(metadataTemp, `${JSON.stringify(metadata)}\n`, { encoding: "utf8", mode: 0o600 });
  await rename(metadataTemp, metadataPath);
  await chmod(metadataPath, 0o600);
}

function isAllowedRequest(request) {
  return config.allowAnonymous || Boolean(request.headers[config.trustedSubjectHeader]);
}

async function serveIonEndpoint(request, response, assetId) {
  if (!config.assetAllowlist.has(assetId)) return writeJson(response, 403, { ok: false, error: "cesium_asset_not_allowed" });
  await ionTokenWriteQueue;
  const cached = await readIonEndpointCache(assetId);
  const cachedState = cached ? ionEndpointCacheState(cached) : null;
  if (config.mode === "offline") {
    if (!cached) return writeJson(response, 504, { ok: false, error: "cesium_ion_offline_endpoint_miss" });
    if (!cachedState.usable) return writeJson(response, 504, { ok: false, error: "cesium_ion_offline_endpoint_expired" });
    if (!isOfflineProviderAllowed(ionEndpointUrl(cached))) return writeJson(response, 409, { ok: false, error: "map_provider_offline_not_permitted" });
    rememberIonEndpoint(cached);
    return writeJson(response, 200, { ok: true, ...publicIonEndpoint(cached), cache: "offline-endpoint-hit" });
  }
  if (!activeCesiumIonToken) {
    if (cached && cachedState.usable) {
      rememberIonEndpoint(cached);
      return writeJson(response, 200, { ok: true, ...publicIonEndpoint(cached), cache: "cached-endpoint-no-master-token" });
    }
    return writeJson(response, 503, { ok: false, error: "cesium_ion_not_configured" });
  }

  const ionReferer = requestIonReferer(request);
  if (cached && cachedState.usable) {
    rememberIonEndpoint(cached);
    if (cachedState.needsRefresh) scheduleIonEndpointRefresh(assetId, ionReferer);
    return writeJson(response, 200, {
      ok: true,
      ...publicIonEndpoint(cached),
      cache: cachedState.needsRefresh ? "ion-endpoint-refresh-ahead" : "ion-endpoint-hit",
    });
  }

  const next = await refreshIonEndpoint(assetId, ionReferer);
  return writeJson(response, 200, { ok: true, ...publicIonEndpoint(next), cache: "ion-endpoint-online" });
}

function scheduleIonEndpointRefresh(assetId, ionReferer = "") {
  const active = activeIonEndpoints.get(String(assetId));
  if (active) {
    const state = ionEndpointCacheState(active);
    if (state.usable && !state.needsRefresh) return;
  }
  void refreshIonEndpoint(assetId, ionReferer).catch((error) => {
    const errorCode = safeDiagnosticCode(error?.message || "cesium_ion_endpoint_refresh_failed");
    if (errorCode === "cesium_ion_token_rotated") return;
    console.warn(JSON.stringify({ event: "map_ion_endpoint_refresh_failed", assetId, error: errorCode }));
  });
}

function refreshIonEndpoint(assetId, ionReferer = "") {
  const token = activeCesiumIonToken;
  if (!token) return Promise.reject(gatewayError("cesium_ion_not_configured", 503));
  const generation = ionTokenGeneration;
  const existing = inflightIonEndpointRefreshes.get(assetId);
  if (existing?.generation === generation) return existing.promise;

  const promise = (async () => {
    const next = await fetchCesiumIonEndpoint(assetId, token, ionReferer);
    // An administrator may rotate the master token while this provider request
    // is in flight. Never re-introduce an endpoint credential minted under the
    // previous token generation.
    if (generation !== ionTokenGeneration || token !== activeCesiumIonToken) {
      throw gatewayError("cesium_ion_token_rotated", 409);
    }
    rememberIonEndpoint(next);
    if (config.mode === "readwrite") await writeIonEndpointCache(next);
    return next;
  })();
  const record = { generation, promise };
  inflightIonEndpointRefreshes.set(assetId, record);
  promise.finally(() => {
    if (inflightIonEndpointRefreshes.get(assetId) === record) inflightIonEndpointRefreshes.delete(assetId);
  }).catch(() => undefined);
  return promise;
}

async function serveCachedUpstream(request, response, requestUrl) {
  const rawTarget = requestUrl.searchParams.get("url");
  if (!rawTarget) return writeJson(response, 400, { ok: false, error: "map_cache_url_required" });
  let target = validateUpstream(rawTarget);
  // Browser-facing route revision only. It must not affect the upstream URL
  // or create a duplicate TileCache object.
  target.searchParams.delete("nodedc_client_revision");
  const cacheProfile = String(target.searchParams.get("nodedc_cache_profile") || "live").toLowerCase();
  target.searchParams.delete("nodedc_cache_profile");
  if (!new Set(["live", "offline"]).has(cacheProfile)) return writeJson(response, 400, { ok: false, error: "map_cache_profile_invalid" });
  const store = cacheProfile === "offline" ? offlineSnapshot : liveCache;
  if (!store) return writeJson(response, 409, { ok: false, error: "map_offline_snapshot_not_configured" });
  const cacheMode = String(target.searchParams.get("nodedc_cache_mode") || "hybrid").toLowerCase();
  target.searchParams.delete("nodedc_cache_mode");
  if (!new Set(["hybrid", "passthrough"]).has(cacheMode)) return writeJson(response, 400, { ok: false, error: "map_cache_mode_invalid" });
  const isLegacyCacheHost = config.legacyCacheHosts.has(target.hostname.toLowerCase());
  const forceRefresh = target.searchParams.get("nodedc_cache_refresh") === "1";
  target.searchParams.delete("nodedc_cache_refresh");
  const ionReferer = requestIonReferer(request);
  const cacheKey = createHash("sha256").update(canonicalCacheUrl(target)).digest("hex");

  // "Live" without cache is still routed through Gateway — it is never a
  // browser-side direct request — but it neither reads from nor writes to the
  // persistent store. The immutable offline profile intentionally has no such
  // bypass mode.
  if (cacheMode === "passthrough" && cacheProfile === "live") {
    gatewayDiagnostics.recordCache("passthrough");
    target = await injectGatewayCredentials(target, ionReferer);
    return proxyUncachedRequest(request, response, target, "live-pass-through", ionReferer);
  }
  const cached = await getCachedEntry(store, cacheKey);
  const fresh = cached && cached.expiresAt > Date.now();

  // A refresh can have a previously published object at this key while its
  // replacement is still streaming into the private temp file. Treat every
  // request that arrives during that window as a follower: otherwise a normal
  // cache-first request could race the atomic rename and receive the old
  // object immediately after the refresh leader received the new one.
  const activeFill = inflightWrites.get(`${store.name}:${cacheKey}`);
  if (activeFill) {
    try {
      const entry = await activeFill.commit;
      const state = cached ? "live-cache-hit" : (forceRefresh ? "live-refresh-record" : "live-record");
      return serveCachedFile(request, response, store, entry, state);
    } catch (error) {
      if (cached) {
        gatewayDiagnostics.recordCache("staleFallback");
        return serveCachedFile(request, response, store, cached, "live-stale-upstream-error");
      }
      throw error;
    }
  }

  // A migrated Engine cache is a local sandbox snapshot, not an instruction to
  // fetch arbitrary historic providers when an object is missing. The profile
  // is selected by the visual adapter; the browser can never turn it into a
  // new upstream fetch by changing a setting.
  if (cacheProfile === "offline" || config.mode === "offline") {
    if (cached) return serveCachedFile(request, response, store, cached, fresh ? `${store.name}-offline-hit` : `${store.name}-offline-stale`);
    return writeJson(response, 504, { ok: false, error: cacheProfile === "offline" ? "map_offline_snapshot_miss" : "map_cache_offline_miss" });
  }
  if (isLegacyCacheHost) {
    if (cached) return serveCachedFile(request, response, store, cached, fresh ? "legacy-cache-hit" : "legacy-cache-stale");
    return writeJson(response, 504, { ok: false, error: "map_legacy_cache_miss" });
  }

  // The product setting "Не перезаписывать cache" is cache-first by
  // definition: a collected tile is served from the shared persistent volume
  // immediately, while only a miss travels through the official live
  // provider. `nodedc_cache_refresh=1` is the explicit opt-in to replace a
  // previously collected object.
  if (cached && !forceRefresh) {
    gatewayDiagnostics.recordCache(fresh ? "hit" : "staleHit");
    return serveCachedFile(request, response, store, cached, fresh ? "live-cache-hit" : "live-cache-stale");
  }

  // Credentials are needed only for a real upstream request. Cache lookup uses
  // the credential-free canonical URL, so a warm TileCache remains independent
  // from Ion token refresh and from the AMD/VPN route.
  target = await injectGatewayCredentials(target, ionReferer);

  if (config.mode === "readonly") {
    gatewayDiagnostics.recordCache("readonlyPassThrough");
    return proxyUncachedRequest(request, response, target, "pass-through-readonly", ionReferer);
  }

  if (String(request.headers.range || "").trim()) {
    gatewayDiagnostics.recordCache(cached ? "rangeRefresh" : "rangeMiss");
    return proxyRangeRequest(request, response, target, cached, store, ionReferer);
  }

  try {
    gatewayDiagnostics.recordCache(forceRefresh && cached ? "refresh" : "miss");
    // Keep the await inside this try block: setup failures (notably an explicit
    // refresh whose provider is temporarily unavailable) must reach the stale
    // fallback below instead of bypassing it as an unobserved returned promise.
    return await streamAndCacheMiss(
      request,
      response,
      target,
      cacheKey,
      store,
      forceRefresh ? "live-refresh-record" : "live-record",
      ionReferer,
    );
  } catch (error) {
    if (isCacheCapacityError(error)) return proxyUncachedRequest(request, response, target, "live-pass-through-cache-full", ionReferer);
    if (cached) {
      gatewayDiagnostics.recordCache("staleFallback");
      return serveCachedFile(request, response, store, cached, "live-stale-upstream-error");
    }
    throw error;
  }
}

function isOfflineProviderAllowed(rawTarget) {
  if (!config.offlineProviderAllowlist.size) return false;
  try {
    const target = new URL(rawTarget);
    return config.offlineProviderAllowlist.has(target.hostname.toLowerCase());
  } catch {
    return false;
  }
}

function validateUpstream(rawTarget) {
  if (rawTarget.length > 8192) throw gatewayError("map_cache_url_too_long", 400);
  let target;
  try { target = new URL(rawTarget); } catch { throw gatewayError("invalid_map_cache_url", 400); }
  const hostname = target.hostname.toLowerCase();

  // Bing's metadata endpoint still returns HTTP tile templates. They are not
  // allowed through as HTTP: upgrade only the known Bing tile hosts before
  // the standard HTTPS and allowlist checks. This keeps the browser and the
  // Gateway on encrypted transport while making the provider contract work.
  if (target.protocol === "http:" && /^(dev\.virtualearth\.net|ecn\.t[0-3]\.tiles\.virtualearth\.net|ecn\.tiles\.virtualearth\.net)$/.test(hostname)) {
    target.protocol = "https:";
  }
  const testLoopbackHttp = config.testAllowHttpLoopback
    && target.protocol === "http:"
    && ["127.0.0.1", "localhost", "::1"].includes(hostname);
  if (target.protocol !== "https:" && !testLoopbackHttp) throw gatewayError("map_cache_https_required", 400);
  if (target.username || target.password || (!config.upstreamAllowlist.has(hostname) && !config.legacyCacheHosts.has(hostname))) {
    throw gatewayError("map_cache_upstream_not_allowed", 403);
  }
  return target;
}

function canonicalCacheUrl(target) {
  const url = stripCredentialQueryParameters(target);
  // The old Engine snapshot distributes identical Bing tile semantics across
  // t0…t3. A viewer can choose a different subdomain for the same quadkey, so
  // use one logical host for the cache key while preserving the original URL
  // only as provenance in the imported index entry.
  if (/^ecn\.t[0-3]\.tiles\.virtualearth\.net$/i.test(url.hostname)) {
    url.hostname = "ecn.tiles.virtualearth.net";
  }
  url.searchParams.sort();
  return url.toString();
}

async function streamAndCacheMiss(request, response, target, cacheKey, store, cacheState, ionReferer = "") {
  const inflightKey = `${store.name}:${cacheKey}`;
  const existing = inflightWrites.get(inflightKey);
  if (existing) {
    const entry = await existing.commit;
    return serveCachedFile(request, response, store, entry, cacheState);
  }

  const setup = prepareStreamingCacheFill(target, cacheKey, store, ionReferer);
  const record = { setup, commit: null };
  record.commit = setup.then(({ cacheCommit }) => cacheCommit);
  inflightWrites.set(inflightKey, record);
  // Cache fill is intentionally server-owned once started. A browser can stop
  // consuming its tee branch without cancelling the upstream/disk branch used
  // by concurrent viewers and future offline hits.
  record.commit.catch((error) => {
    const errorCode = safeDiagnosticCode(error?.message || "map_cache_fill_failed");
    gatewayDiagnostics.recordGatewayFailure(errorCode);
    console.warn(JSON.stringify({ event: "map_cache_fill_failed", error: errorCode }));
  });
  record.commit.finally(() => {
    if (inflightWrites.get(inflightKey) === record) inflightWrites.delete(inflightKey);
  }).catch(() => undefined);

  const prepared = await setup;
  if (request.method === "HEAD") {
    await prepared.clientBody.cancel().catch(() => undefined);
    const entry = await record.commit;
    return serveCachedFile(request, response, store, entry, cacheState);
  }
  if (request.aborted || response.destroyed) {
    await prepared.clientBody.cancel().catch(() => undefined);
    return;
  }

  copyUpstreamHeaders(response, prepared.headers);
  response.setHeader("x-nodedc-map-cache", cacheState);
  response.writeHead(prepared.status);
  try {
    await pipeline(Readable.fromWeb(prepared.clientBody), response);
  } catch (error) {
    if (request.aborted || response.destroyed || response.writableEnded) return;
    throw error;
  }
}

async function prepareStreamingCacheFill(target, cacheKey, store, ionReferer = "") {
  const upstream = await fetchWithTimeout(target, {
    headers: { accept: "application/json, application/octet-stream, image/*, */*;q=0.5", ...ionRefererHeaders(ionReferer) },
  }, { operation: "cache-fill" });
  if (!upstream.ok || !upstream.body) {
    await upstream.body?.cancel().catch(() => undefined);
    throw gatewayError("map_upstream_unavailable", upstream.status || 502);
  }
  const expectedBytes = Number.parseInt(upstream.headers.get("content-length") || "", 10);
  if (Number.isFinite(expectedBytes) && expectedBytes > config.maxObjectBytes) {
    await upstream.body.cancel().catch(() => undefined);
    throw gatewayError("map_cache_object_too_large", 413);
  }

  const [clientBody, cacheBody] = upstream.body.tee();
  const cacheCommit = commitCacheBody(cacheBody, upstream.headers, cacheKey, store);
  return { status: upstream.status, headers: upstream.headers, clientBody, cacheCommit };
}

async function commitCacheBody(cacheBody, upstreamHeaders, cacheKey, store) {
  const relativePath = join("objects", cacheKey.slice(0, 2), `${cacheKey}.bin`);
  const filePath = join(store.dir, relativePath);
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
  await mkdir(dirname(filePath), { recursive: true });
  let bytes = 0;
  const limit = new Transform({
    transform(chunk, _encoding, callback) {
      bytes += chunk.length;
      if (bytes > config.maxObjectBytes) return callback(gatewayError("map_cache_object_too_large", 413));
      callback(null, chunk);
    },
  });
  let reservationHeld = false;
  try {
    await pipeline(Readable.fromWeb(cacheBody), limit, createWriteStream(tempPath, { flags: "wx" }));
    const replacedBytes = Number(store.index.entries[cacheKey]?.bytes || 0);
    if (!(await reserveCacheCapacity(store, bytes, replacedBytes))) throw cacheCapacityError();
    reservationHeld = true;
    await rename(tempPath, filePath);
    const now = Date.now();
    const entry = {
      key: cacheKey,
      file: relativePath,
      bytes,
      contentType: upstreamHeaders.get("content-type") || "application/octet-stream",
      etag: upstreamHeaders.get("etag") || null,
      savedAt: now,
      lastAccessAt: now,
      expiresAt: now + responseTtl(upstreamHeaders.get("cache-control")),
    };
    store.index.entries[cacheKey] = entry;
    await releaseCacheCapacity(store, bytes);
    reservationHeld = false;
    await writeCacheIndex(store);
    return entry;
  } catch (error) {
    if (reservationHeld) await releaseCacheCapacity(store, bytes);
    await rm(tempPath, { force: true });
    throw error;
  }
}

function responseTtl(cacheControl) {
  const match = String(cacheControl || "").match(/max-age=(\d+)/i);
  if (!match) return config.defaultTtlMs;
  return Math.min(Number.parseInt(match[1], 10) * 1000, config.defaultTtlMs);
}

async function proxyRangeRequest(request, response, target, cached, store, ionReferer = "") {
  const upstream = await fetchWithTimeout(target, {
    headers: { range: String(request.headers.range), accept: "*/*", ...ionRefererHeaders(ionReferer) },
  }, { operation: "range" });
  if (!upstream.ok || !upstream.body) {
    if (cached) return serveCachedFile(request, response, store, cached, "stale-range-error");
    throw gatewayError("map_upstream_range_unavailable", upstream.status || 502);
  }
  copyUpstreamHeaders(response, upstream.headers);
  response.setHeader("x-nodedc-map-cache", "range-pass-through");
  response.writeHead(upstream.status);
  await pipeline(Readable.fromWeb(upstream.body), response);
}

async function proxyUncachedRequest(request, response, target, cacheState, ionReferer = "") {
  const upstream = await fetchWithTimeout(target, {
    headers: {
      accept: String(request.headers.accept || "application/json, application/octet-stream, image/*, */*;q=0.5"),
      ...(request.headers.range ? { range: String(request.headers.range) } : {}),
      ...ionRefererHeaders(ionReferer),
    },
  }, { operation: "passthrough" });
  if (!upstream.ok || !upstream.body) throw gatewayError("map_upstream_unavailable", upstream.status || 502);
  copyUpstreamHeaders(response, upstream.headers);
  response.setHeader("x-nodedc-map-cache", cacheState);
  response.writeHead(upstream.status);
  if (request.method === "HEAD") return response.end();
  await pipeline(Readable.fromWeb(upstream.body), response);
}

async function serveCachedFile(request, response, store, entry, state) {
  const filePath = join(store.dir, entry.file);
  const info = await stat(filePath);
  const range = parseRange(request.headers.range, info.size);
  const etag = cacheEntityTag(entry, info.size);
  response.setHeader("content-type", entry.contentType);
  response.setHeader("accept-ranges", "bytes");
  response.setHeader("cache-control", "public, max-age=60");
  response.setHeader("etag", etag);
  response.setHeader("x-nodedc-map-cache", state);
  response.setHeader("x-nodedc-map-cache-age", String(Math.max(0, Math.round((Date.now() - entry.savedAt) / 1000))));
  // This store has append-only/no-eviction policy. A hit therefore has no
  // persistent metadata to update: rewriting the complete JSON index for every
  // 20 KB tile made warm views slower than the provider itself.
  if (ifNoneMatchMatches(request.headers["if-none-match"], etag)) {
    response.writeHead(304);
    return response.end();
  }

  if (!range) {
    response.setHeader("content-length", info.size);
    response.writeHead(200);
    if (request.method === "HEAD") return response.end();
    return pipeline(createReadStream(filePath), response);
  }
  response.setHeader("content-length", range.end - range.start + 1);
  response.setHeader("content-range", `bytes ${range.start}-${range.end}/${info.size}`);
  response.writeHead(206);
  if (request.method === "HEAD") return response.end();
  return pipeline(createReadStream(filePath, range), response);
}

function cacheEntityTag(entry, size) {
  const providerTag = String(entry.etag || "").trim();
  if (providerTag.length <= 1024 && /^(?:W\/)?"[^"\r\n]*"$/.test(providerTag)) return providerTag;
  const key = /^[a-f0-9]{64}$/i.test(String(entry.key || "")) ? String(entry.key).slice(0, 32).toLowerCase() : "object";
  const revision = Number.isFinite(Number(entry.savedAt)) ? Math.max(0, Number(entry.savedAt)).toString(36) : "0";
  return `"nodedc-${key}-${Number(size).toString(16)}-${revision}"`;
}

function ifNoneMatchMatches(rawHeader, etag) {
  const raw = String(rawHeader || "");
  if (!raw || raw.length > 8192) return false;
  const expected = etag.replace(/^W\//, "");
  return raw.split(",").some((part) => {
    const candidate = part.trim();
    return candidate === "*" || candidate.replace(/^W\//, "") === expected;
  });
}

function parseRange(header, size) {
  if (!header) return null;
  const match = String(header).match(/^bytes=(\d*)-(\d*)$/);
  if (!match) return null;
  const start = match[1] ? Number.parseInt(match[1], 10) : 0;
  const end = match[2] ? Number.parseInt(match[2], 10) : size - 1;
  if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end >= size) return null;
  return { start, end };
}

async function getCachedEntry(store, cacheKey) {
  const entry = store.index.entries[cacheKey];
  if (!entry) return null;
  try {
    const info = await stat(join(store.dir, entry.file));
    if (!info.isFile()) throw new Error("not_a_file");
    return entry;
  } catch {
    if (store.mutable) {
      delete store.index.entries[cacheKey];
      await writeCacheIndex(store);
    }
    return null;
  }
}

function createCacheStore(name, dir, mutable) {
  return {
    name,
    dir,
    mutable,
    objectsDir: join(dir, "objects"),
    ionEndpointsDir: join(dir, "ion-endpoints"),
    indexPath: join(dir, "index.json"),
    index: { version: 1, entries: {} },
    indexRevision: 0,
    persistedIndexRevision: 0,
    indexWriteScheduled: false,
    indexWriteRunning: false,
    indexWriteWaiters: [],
    reservedBytes: 0,
    capacityQueue: Promise.resolve(),
  };
}

async function initialiseCacheStore(store) {
  if (store.mutable) {
    await mkdir(store.objectsDir, { recursive: true });
    await mkdir(store.ionEndpointsDir, { recursive: true });
  }
  store.index = await readCacheIndex(store);
}

async function readCacheIndex(store) {
  try {
    const parsed = JSON.parse(await readFile(store.indexPath, "utf8"));
    if (parsed?.version === 1 && parsed.entries && typeof parsed.entries === "object") return parsed;
  } catch (error) {
    if (error?.code !== "ENOENT") console.warn("Map cache index ignored: invalid file");
  }
  return { version: 1, entries: {} };
}

function ionEndpointPath(assetId) {
  return join(liveCache.ionEndpointsDir, `${assetId}.json`);
}

async function readIonEndpointCache(assetId) {
  try {
    const value = JSON.parse(await readFile(ionEndpointPath(assetId), "utf8"));
    if (value?.assetId === assetId && ((value?.url && value?.accessToken) || (value?.externalType === "BING" && value?.options?.url && value?.options?.key))) return value;
  } catch (error) {
    if (error?.code !== "ENOENT") console.warn("Ion endpoint cache ignored: invalid file");
  }
  return null;
}

async function writeIonEndpointCache(endpoint) {
  const target = ionEndpointPath(endpoint.assetId);
  const temp = `${target}.${process.pid}.tmp`;
  await writeFile(temp, `${JSON.stringify(endpoint)}\n`, { encoding: "utf8", mode: 0o600 });
  await rename(temp, target);
}

function rememberIonEndpoint(endpoint) {
  if (endpoint?.assetId === undefined) return;
  const assetId = String(endpoint.assetId);
  const current = activeIonEndpoints.get(assetId);
  if (!current || Number(endpoint.savedAt || 0) >= Number(current.savedAt || 0)) activeIonEndpoints.set(assetId, endpoint);
}

function ionEndpointCacheState(endpoint, now = Date.now()) {
  const savedAt = Number(endpoint?.savedAt || 0);
  const credentialExpiresAt = ionEndpointCredentialExpiry(endpoint);
  const usable = credentialExpiresAt === null || now < credentialExpiresAt;
  const ttlRefreshAt = savedAt > 0
    ? savedAt + Math.max(0, config.ionEndpointTtlMs - config.ionEndpointRefreshAheadMs)
    : 0;
  const credentialRefreshAt = credentialExpiresAt === null
    ? Number.POSITIVE_INFINITY
    : Math.max(0, credentialExpiresAt - config.ionEndpointRefreshAheadMs);
  return {
    usable,
    needsRefresh: !savedAt || now >= Math.min(ttlRefreshAt, credentialRefreshAt),
    credentialExpiresAt,
  };
}

function ionEndpointCredentialExpiry(endpoint) {
  if (endpoint?.externalType === "BING") return null;
  const token = String(endpoint?.accessToken || "");
  const parts = token.split(".");
  if (parts.length !== 3 || !parts[1]) return null;
  try {
    const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
    const seconds = Number(payload?.exp);
    return Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds * 1000) : null;
  } catch {
    return null;
  }
}

function ionEndpointUrl(endpoint) {
  return endpoint?.externalType === "BING" ? endpoint.options?.url : endpoint?.url;
}

function publicIonEndpoint(endpoint) {
  const shared = {
    assetId: endpoint.assetId,
    type: endpoint.type,
    attributions: Array.isArray(endpoint.attributions) ? endpoint.attributions : [],
    credentialMode: "gateway",
  };
  if (endpoint.externalType === "BING") {
    return {
      ...shared,
      externalType: "BING",
      options: {
        url: publicProviderUrl(endpoint.options?.url),
        mapStyle: String(endpoint.options?.mapStyle || "Aerial"),
      },
    };
  }
  return { ...shared, url: publicProviderUrl(endpoint.url) };
}

function publicProviderUrl(rawUrl) {
  return stripCredentialQueryParameters(validateUpstream(String(rawUrl || ""))).toString();
}

function stripCredentialQueryParameters(target) {
  const sanitized = new URL(target.toString());
  for (const key of [...sanitized.searchParams.keys()]) {
    if (credentialQueryKeys.has(key.toLowerCase())) sanitized.searchParams.delete(key);
  }
  return sanitized;
}

async function injectGatewayCredentials(target, ionReferer = "") {
  await ionTokenWriteQueue;
  const upstream = stripCredentialQueryParameters(target);
  const endpoints = await knownIonEndpoints();
  let matchedIon = endpoints
    .filter((endpoint) => endpoint.externalType !== "BING" && endpoint.accessToken)
    .map((endpoint) => ({ endpoint, scopeLength: endpointResourceScopeLength(upstream, endpoint.url) }))
    .filter(({ scopeLength }) => scopeLength >= 0)
    .sort((left, right) => right.scopeLength - left.scopeLength)[0]?.endpoint;
  if (matchedIon) {
    const state = ionEndpointCacheState(matchedIon);
    if (!state.usable) {
      const refreshed = await refreshIonEndpoint(String(matchedIon.assetId), ionReferer);
      if (endpointResourceScopeLength(upstream, refreshed.url) < 0) throw gatewayError("cesium_ion_endpoint_scope_changed", 409);
      matchedIon = refreshed;
    } else if (state.needsRefresh) {
      scheduleIonEndpointRefresh(String(matchedIon.assetId), ionReferer);
    }
  }
  if (matchedIon) upstream.searchParams.set("access_token", matchedIon.accessToken);

  const bing = endpoints.find((endpoint) => endpoint.externalType === "BING" && endpoint.options?.key);
  if (bing && isBingResource(upstream)) upstream.searchParams.set("key", bing.options.key);
  return upstream;
}

async function knownIonEndpoints() {
  for (const assetId of config.assetAllowlist) {
    if (activeIonEndpoints.has(assetId)) continue;
    const cached = await readIonEndpointCache(assetId);
    if (cached) rememberIonEndpoint(cached);
  }
  return [...activeIonEndpoints.values()];
}

function endpointResourceScopeLength(target, endpointUrl) {
  if (!endpointUrl) return -1;
  try {
    const endpoint = new URL(endpointUrl);
    if (target.origin !== endpoint.origin) return -1;
    if (target.pathname === endpoint.pathname) return 1_000_000 + endpoint.pathname.length;
    // Ion may return a root file such as `tileset.json`; its child URIs resolve
    // beside that file, not below `tileset.json/`. The directory is trusted only
    // because this endpoint and credential came from the private Ion response.
    // Never broaden a root-level file into an origin-wide credential scope.
    const directoryPath = new URL(".", endpoint).pathname;
    if (directoryPath === "/" || !target.pathname.startsWith(directoryPath)) return -1;
    return directoryPath.length;
  } catch {
    return -1;
  }
}

function isBingResource(target) {
  return /(^|\.)virtualearth\.net$/i.test(target.hostname);
}

function writeCacheIndex(store) {
  if (!store.mutable) return Promise.resolve();
  const revision = ++store.indexRevision;
  const durable = new Promise((resolve, reject) => store.indexWriteWaiters.push({ revision, resolve, reject }));
  scheduleCacheIndexWrite(store);
  return durable;
}

function scheduleCacheIndexWrite(store) {
  if (store.indexWriteScheduled || store.indexWriteRunning) return;
  store.indexWriteScheduled = true;
  // Batch a short burst of neighbouring I/O completions. A Cesium viewport can
  // finish dozens of distinct tiles together; serialising the full index once
  // per tile is unnecessary because one later snapshot contains every entry.
  setTimeout(() => {
    store.indexWriteScheduled = false;
    void flushCacheIndex(store);
  }, 10);
}

async function flushCacheIndex(store) {
  if (store.indexWriteRunning) return;
  store.indexWriteRunning = true;
  try {
    while (store.persistedIndexRevision < store.indexRevision) {
      const snapshotRevision = store.indexRevision;
      const snapshot = `${JSON.stringify(store.index)}\n`;
      const tempPath = `${store.indexPath}.${process.pid}.${snapshotRevision}.tmp`;
      await writeFile(tempPath, snapshot, "utf8");
      await rename(tempPath, store.indexPath);
      store.persistedIndexRevision = snapshotRevision;
      gatewayDiagnostics.recordIndexSnapshot();
      settleIndexWaiters(store);
    }
  } catch (error) {
    console.warn("Map cache index write failed", error instanceof Error ? error.message : "unknown");
    const waiters = store.indexWriteWaiters.splice(0);
    for (const waiter of waiters) waiter.reject(error);
  } finally {
    store.indexWriteRunning = false;
    if (store.indexWriteWaiters.length && store.persistedIndexRevision < store.indexRevision) scheduleCacheIndexWrite(store);
  }
}

function settleIndexWaiters(store) {
  const pending = [];
  for (const waiter of store.indexWriteWaiters) {
    if (waiter.revision <= store.persistedIndexRevision) waiter.resolve();
    else pending.push(waiter);
  }
  store.indexWriteWaiters = pending;
}

function cacheBytes(store) {
  return Object.values(store.index.entries).reduce((sum, entry) => sum + Number(entry.bytes || 0), 0);
}

function reserveCacheCapacity(store, bytes, replacedBytes = 0) {
  const action = store.capacityQueue.then(() => {
    const used = Math.max(0, cacheBytes(store) - replacedBytes) + store.reservedBytes;
    if (used + bytes > config.maxCacheBytes) return false;
    store.reservedBytes += bytes;
    return true;
  });
  store.capacityQueue = action.then(() => undefined, () => undefined);
  return action;
}

function releaseCacheCapacity(store, bytes) {
  const action = store.capacityQueue.then(() => {
    store.reservedBytes = Math.max(0, store.reservedBytes - bytes);
  });
  store.capacityQueue = action.then(() => undefined, () => undefined);
  return action;
}

function cacheCapacityError() {
  return gatewayError("map_cache_capacity_reached", 507);
}

function isCacheCapacityError(error) {
  return error?.message === "map_cache_capacity_reached";
}

async function cacheStats(store) {
  const entries = Object.values(store.index.entries);
  const bytes = cacheBytes(store);
  return {
    name: store.name,
    mode: store.mutable ? config.mode : "readonly",
    writePolicy: store.mutable ? "append-only-no-eviction" : "readonly",
    entries: entries.length,
    bytes,
    maxBytes: store.mutable ? config.maxCacheBytes : null,
    atCapacity: store.mutable ? bytes >= config.maxCacheBytes : false,
    persistent: true,
  };
}

function shouldUseCesiumEgress(target) {
  if (!config.mapEgress.url) return false;
  try {
    return config.cesiumEgressHosts.has(new URL(String(target)).hostname.toLowerCase());
  } catch {
    return false;
  }
}

function throughCesiumEgress(target, options = {}) {
  const upstreamUrl = new URL(String(target));
  const proxyUrl = new URL("proxy/cesium/fetch", config.mapEgress.url);
  proxyUrl.searchParams.set("url", upstreamUrl.toString());
  const headers = new Headers(options.headers || {});
  const authorization = headers.get("authorization");
  const referer = headers.get("referer");
  headers.delete("authorization");
  headers.delete("referer");
  headers.set("x-proxy-token", config.mapEgress.proxyToken);
  if (authorization) headers.set("x-nodedc-cesium-authorization", authorization);
  if (referer) headers.set("x-nodedc-map-referer", referer);
  return { target: proxyUrl, options: { ...options, headers } };
}

async function fetchWithTimeout(target, options = {}, diagnostic = {}) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), config.upstreamTimeoutMs);
  const viaEgress = shouldUseCesiumEgress(target);
  const provider = safeProviderName(target);
  const operation = safeProviderOperation(diagnostic.operation);
  const assetId = /^\d{1,20}$/.test(String(diagnostic.assetId || "")) ? String(diagnostic.assetId) : undefined;
  const request = viaEgress
    ? throughCesiumEgress(target, options)
    : { target, options };
  const startedAt = Date.now();
  try {
    const response = await fetch(request.target, { ...request.options, signal: controller.signal, redirect: "follow" });
    const egressError = safeEgressErrorCode(response.headers.get("x-nodedc-egress-error"));
    // The restricted egress emits a short code instead of a target URL or a
    // secret. Preserve it through Map Gateway so the operator can distinguish
    // an AMD/VPN timeout from a real Cesium provider response.
    if (egressError) {
      await response.body?.cancel().catch(() => undefined);
      throw gatewayError(`map_egress_${egressError}`, response.status || 502);
    }
    const durationMs = Date.now() - startedAt;
    gatewayDiagnostics.recordUpstream({
      viaEgress,
      status: response.status,
      durationMs,
      error: "",
    });
    if (response.status >= 400) {
      console.warn(JSON.stringify({
        event: "map_provider_http_failure",
        provider,
        operation,
        ...(assetId ? { assetId } : {}),
        status: response.status,
        viaEgress,
        durationMs,
      }));
    }
    return response;
  } catch (error) {
    const errorCode = error?.message === "map_egress_amd_connector_timeout" || error?.message === "map_egress_amd_upstream_tls_timeout"
      ? error.message
      : error?.name === "AbortError"
        ? "map_upstream_timeout"
        : error?.message?.startsWith?.("map_egress_")
          ? error.message
          : "map_upstream_fetch_failed";
    const durationMs = Date.now() - startedAt;
    gatewayDiagnostics.recordUpstream({
      viaEgress,
      status: Number(error?.statusCode || 502),
      durationMs,
      error: errorCode,
    });
    console.warn(JSON.stringify({
      event: "map_provider_transport_failure",
      provider,
      operation,
      ...(assetId ? { assetId } : {}),
      error: safeDiagnosticCode(errorCode),
      viaEgress,
      durationMs,
    }));
    throw gatewayError(errorCode, Number(error?.statusCode || 502));
  }
  finally { clearTimeout(timeout); }
}

function copyUpstreamHeaders(response, headers) {
  // Node's fetch transparently decodes a compressed upstream body, but retains
  // the upstream Content-Length header. Forwarding that encoded byte length
  // alongside the decoded stream truncates JSON (notably Cesium layer.json)
  // in the browser. Cached responses calculate their own exact length; live
  // pass-through responses intentionally use HTTP chunking instead.
  for (const name of ["content-type", "content-range", "accept-ranges", "etag", "last-modified"]) {
    const value = headers.get(name);
    if (value) response.setHeader(name, value);
  }
}

function writeJson(response, status, value) {
  response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
  response.end(JSON.stringify(value));
}

function gatewayError(message, statusCode) {
  const error = new Error(message);
  error.statusCode = statusCode;
  return error;
}

function createGatewayDiagnostics(slowUpstreamMs) {
  const metrics = {
    cacheHits: 0,
    cacheStaleHits: 0,
    cacheMisses: 0,
    cacheRefreshes: 0,
    cachePassthroughs: 0,
    cacheStaleFallbacks: 0,
    upstreamRequests: 0,
    egressRequests: 0,
    upstreamFailures: 0,
    slowUpstreamRequests: 0,
    indexSnapshots: 0,
    lastFailure: null,
    lastFailureAt: null,
  };
  const recordFailure = (error) => {
    if (!error) return;
    metrics.lastFailure = safeDiagnosticCode(error);
    metrics.lastFailureAt = new Date().toISOString();
  };
  return {
    recordCache(state) {
      if (state === "hit") metrics.cacheHits += 1;
      else if (state === "staleHit") metrics.cacheStaleHits += 1;
      else if (state === "refresh") metrics.cacheRefreshes += 1;
      else if (state === "passthrough" || state === "readonlyPassThrough" || state === "rangeMiss" || state === "rangeRefresh") metrics.cachePassthroughs += 1;
      else if (state === "staleFallback") metrics.cacheStaleFallbacks += 1;
      else metrics.cacheMisses += 1;
    },
    recordUpstream({ viaEgress, status, durationMs, error }) {
      metrics.upstreamRequests += 1;
      if (viaEgress) metrics.egressRequests += 1;
      if (durationMs >= slowUpstreamMs) metrics.slowUpstreamRequests += 1;
      if (Number(status) >= 400 || error) {
        metrics.upstreamFailures += 1;
        recordFailure(error || `upstream_http_${status}`);
      }
    },
    recordGatewayFailure(error) {
      recordFailure(error);
    },
    recordIndexSnapshot() {
      metrics.indexSnapshots += 1;
    },
    status() {
      return { ...metrics };
    },
  };
}

function safeEgressErrorCode(value) {
  const code = String(value || "").trim();
  return /^amd_[a-z0-9_.:-]{1,100}$/.test(code) ? code : "";
}

function safeDiagnosticCode(value) {
  return String(value || "map_gateway_error").replace(/[^A-Za-z0-9_.:-]/g, "_").slice(0, 120) || "map_gateway_error";
}

function safeProviderName(target) {
  try {
    const host = new URL(String(target)).hostname.toLowerCase();
    if (host === "api.cesium.com") return "cesium-ion-api";
    if (host === "assets.ion.cesium.com") return "cesium-ion-assets";
    if (/(^|\.)virtualearth\.net$/.test(host)) return "bing-imagery";
    return "allowlisted-provider";
  } catch {
    return "unknown-provider";
  }
}

function safeProviderOperation(value) {
  const operation = String(value || "resource").trim().toLowerCase();
  return /^(?:resource|ion-endpoint|cache-fill|range|passthrough)$/.test(operation) ? operation : "resource";
}
