import assert from "node:assert/strict";
import { execFile as execFileCallback, spawn } from "node:child_process";
import { once } from "node:events";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { createServer as createHttpServer, request as createHttpRequest } from "node:http";
import { createServer as createHttpsServer } from "node:https";
import { connect, createServer as createNetServer } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";

const execFile = promisify(execFileCallback);
const root = await mkdtemp(join(tmpdir(), "nodedc-amd-pool-smoke-"));
const upstreamPort = await freePort();
const connectorPort = await freePort();
const proxyPort = await freePort();
const upstreamSockets = new Set();
const connectorSockets = new Set();
let upstream;
let connector;
let proxy;
let proxyOutput = "";
let tunnelCount = 0;
let upstreamRequests = 0;
let retryAttempts = 0;
let nonReusedResetAttempts = 0;
let retryLimitAttempts = 0;
let retryAbortAttempts = 0;
let queuedAbortAttempts = 0;
let nextConnectorDelayMs = 15;
const redirectCredentials = {};
let holdReleased = false;
let releaseHold;
let markHoldStarted;
let markPreHeaderSeen;
let markPreHeaderClosed;
let markIdleClosed;
let markDelayedConnectorSeen;
let markDelayedConnectorClosed;
let markRedirectSeen;
let markRedirectClosed;
let markRetryAbortSecondSeen;
let markRetryAbortSecondClosed;
const holdRelease = new Promise((resolve) => { releaseHold = resolve; });
const holdStarted = new Promise((resolve) => { markHoldStarted = resolve; });
const preHeaderSeen = new Promise((resolve) => { markPreHeaderSeen = resolve; });
const preHeaderClosed = new Promise((resolve) => { markPreHeaderClosed = resolve; });
const idleClosed = new Promise((resolve) => { markIdleClosed = resolve; });
const delayedConnectorSeen = new Promise((resolve) => { markDelayedConnectorSeen = resolve; });
const delayedConnectorClosed = new Promise((resolve) => { markDelayedConnectorClosed = resolve; });
const redirectSeen = new Promise((resolve) => { markRedirectSeen = resolve; });
const redirectClosed = new Promise((resolve) => { markRedirectClosed = resolve; });
const retryAbortSecondSeen = new Promise((resolve) => { markRetryAbortSecondSeen = resolve; });
const retryAbortSecondClosed = new Promise((resolve) => { markRetryAbortSecondClosed = resolve; });

try {
  const keyPath = join(root, "key.pem");
  const certificatePath = join(root, "certificate.pem");
  await execFile("openssl", [
    "req", "-x509", "-newkey", "rsa:2048", "-nodes",
    "-keyout", keyPath,
    "-out", certificatePath,
    "-subj", "/CN=api.cesium.com",
    "-days", "1",
  ]);

  upstream = createHttpsServer({ key: await readFile(keyPath), cert: await readFile(certificatePath) }, (request, response) => {
    upstreamRequests += 1;
    if (request.url === "/retry") {
      retryAttempts += 1;
      if (retryAttempts === 1) {
        setTimeout(() => request.socket.destroy(), 25);
        return;
      }
      response.writeHead(200, { "content-type": "text/plain" });
      response.end("retry-ok");
      return;
    }
    if (request.url?.startsWith("/hold/")) {
      markHoldStarted();
      const finish = () => {
        response.writeHead(200, { "content-type": "text/plain" });
        response.end(`hold-${request.url.slice("/hold/".length)}`);
      };
      if (holdReleased) finish();
      else void holdRelease.then(finish);
      return;
    }
    if (request.url === "/preheader") {
      markPreHeaderSeen();
      request.socket.once("close", markPreHeaderClosed);
      return;
    }
    if (request.url === "/redirect-hold") {
      markRedirectSeen();
      request.socket.once("close", markRedirectClosed);
      response.writeHead(302, { location: "https://api.cesium.com/redirect-target", "content-type": "text/plain" });
      response.write("redirect-partial");
      return;
    }
    if (request.url === "/redirect-same-origin") {
      redirectCredentials.sameSource = request.headers.authorization;
      response.writeHead(302, { location: "/same-origin-target" });
      response.end("same-origin-redirect");
      return;
    }
    if (request.url === "/same-origin-target") {
      redirectCredentials.sameTarget = request.headers.authorization;
      response.writeHead(200, { "content-type": "text/plain" });
      response.end("same-origin-ok");
      return;
    }
    if (request.url === "/redirect-assets-origin") {
      redirectCredentials.assetsSource = request.headers.authorization;
      response.writeHead(302, { location: "https://assets.ion.cesium.com/assets-origin-target" });
      response.end("assets-origin-redirect");
      return;
    }
    if (request.url === "/assets-origin-target") {
      redirectCredentials.assetsHost = request.headers.host;
      redirectCredentials.assetsTarget = request.headers.authorization;
      response.writeHead(200, { "content-type": "text/plain" });
      response.end("assets-origin-ok");
      return;
    }
    if (request.url === "/redirect-bing-origin") {
      redirectCredentials.bingSource = request.headers.authorization;
      response.writeHead(302, { location: "https://dev.virtualearth.net/bing-origin-target" });
      response.end("bing-origin-redirect");
      return;
    }
    if (request.url === "/bing-origin-target") {
      redirectCredentials.bingHost = request.headers.host;
      redirectCredentials.bingTarget = request.headers.authorization;
      response.writeHead(200, { "content-type": "text/plain" });
      response.end("bing-origin-ok");
      return;
    }
    if (request.url === "/non-reused-reset") {
      nonReusedResetAttempts += 1;
      setTimeout(() => request.socket.destroy(), 25);
      return;
    }
    if (request.url === "/queued-abort") {
      queuedAbortAttempts += 1;
      response.writeHead(200, { "content-type": "text/plain" });
      response.end("queue-abort-reached-upstream");
      return;
    }
    if (request.url === "/retry-limit") {
      retryLimitAttempts += 1;
      setTimeout(() => request.socket.destroy(), 25);
      return;
    }
    if (request.url === "/retry-abort") {
      retryAbortAttempts += 1;
      if (retryAbortAttempts === 1) {
        setTimeout(() => request.socket.destroy(), 25);
        return;
      }
      markRetryAbortSecondSeen();
      request.socket.once("close", markRetryAbortSecondClosed);
      return;
    }
    if (request.url === "/idle") {
      request.socket.once("close", markIdleClosed);
      response.writeHead(200, { "content-type": "text/plain" });
      response.write("partial");
      return;
    }
    const responseNumber = upstreamRequests;
    const finish = () => {
      response.writeHead(200, { "content-type": "text/plain" });
      response.end(`tile-${responseNumber}`);
    };
    if (request.url?.startsWith("/tile/0?")) setTimeout(finish, 25);
    else finish();
  });
  upstream.on("connection", (socket) => {
    upstreamSockets.add(socket);
    socket.once("close", () => upstreamSockets.delete(socket));
  });
  upstream.listen(upstreamPort, "127.0.0.1");
  await once(upstream, "listening");

  connector = createHttpServer((_request, response) => {
    response.writeHead(405);
    response.end();
  });
  connector.on("connection", (socket) => {
    connectorSockets.add(socket);
    socket.once("close", () => connectorSockets.delete(socket));
  });
  connector.on("connect", (_request, clientSocket, head) => {
    tunnelCount += 1;
    // The fake connector deliberately ignores the requested hostname and
    // connects to a local TLS fixture. Production still enforces the strict
    // Cesium/Bing allowlist before opening this tunnel.
    const connectorDelayMs = nextConnectorDelayMs;
    nextConnectorDelayMs = 15;
    if (connectorDelayMs > 100) {
      markDelayedConnectorSeen();
      clientSocket.once("close", markDelayedConnectorClosed);
    }
    const target = connect(upstreamPort, "127.0.0.1");
    target.once("connect", () => {
      setTimeout(() => {
        if (clientSocket.destroyed || target.destroyed) return;
        clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
        if (head.length) target.write(head);
        clientSocket.pipe(target);
        target.pipe(clientSocket);
      }, connectorDelayMs);
    });
    target.once("error", () => clientSocket.destroy());
    clientSocket.once("error", () => target.destroy());
    clientSocket.once("close", () => target.destroy());
  });
  connector.listen(connectorPort, "127.0.0.1");
  await once(connector, "listening");

  const connectorToken = `connector-secret-${"a".repeat(48)}`;
  const mapToken = "map-pool-smoke-secret";
  const providerToken = "provider-secret-marker";
  const connectorTokenFile = join(root, "connector-token");
  const mapTokenFile = join(root, "map-token");
  await writeFile(connectorTokenFile, `${connectorToken}\n`, { mode: 0o600 });
  await writeFile(mapTokenFile, `${mapToken}\n`, { mode: 0o600 });

  proxy = spawn(process.execPath, ["server.mjs"], {
    cwd: new URL("..", import.meta.url),
    env: {
      ...process.env,
      NODE_ENV: "test",
      PORT: String(proxyPort),
      DC_AMD_PROXY_BIND_ADDRESS: "127.0.0.1",
      DC_AMD_CONNECTOR_HOST: "127.0.0.1",
      DC_AMD_CONNECTOR_PORT: String(connectorPort),
      DC_AMD_PAIR_ALLOWED_SOURCE: "127.0.0.1",
      DC_AMD_CONNECTOR_TOKEN_FILE: connectorTokenFile,
      DC_AMD_MAP_EGRESS_TOKEN_FILE: mapTokenFile,
      DC_AMD_PROXY_TEST_ALLOW_INSECURE_TLS: "true",
      DC_AMD_POOL_MAX_SOCKETS: "1",
      DC_AMD_POOL_MAX_FREE_SOCKETS: "1",
      DC_AMD_SLOW_REQUEST_SECONDS: "30",
      DC_AMD_UPSTREAM_TIMEOUT_SECONDS: "2",
      DC_AMD_BODY_IDLE_TIMEOUT_SECONDS: "1",
    },
    stdio: ["ignore", "pipe", "pipe"],
  });
  proxy.stdout.on("data", (chunk) => { proxyOutput += String(chunk); });
  proxy.stderr.on("data", (chunk) => { proxyOutput += String(chunk); });
  await waitForProxy(proxyPort, proxy, () => proxyOutput);

  for (let index = 0; index < 4; index += 1) {
    const target = `https://api.cesium.com/tile/${index}${index === 0 ? `?access_token=${providerToken}` : ""}`;
    const response = await proxyFetch(proxyPort, mapToken, target);
    assert.equal(response.status, 200);
    assert.equal(await response.text(), `tile-${index + 1}`);
  }
  assert.equal(upstreamRequests, 4);
  assert.equal(tunnelCount, 1);

  const retryResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/retry");
  assert.equal(retryResponse.status, 200);
  assert.equal(await retryResponse.text(), "retry-ok");
  assert.equal(retryAttempts, 2);

  const redirectAuthorization = "Bearer master-api-secret-marker";
  const redirectHeaders = { "x-nodedc-cesium-authorization": redirectAuthorization };
  const sameOriginResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/redirect-same-origin", undefined, redirectHeaders);
  assert.equal(sameOriginResponse.status, 200);
  assert.equal(await sameOriginResponse.text(), "same-origin-ok");
  const assetsOriginResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/redirect-assets-origin", undefined, redirectHeaders);
  assert.equal(assetsOriginResponse.status, 200);
  assert.equal(await assetsOriginResponse.text(), "assets-origin-ok");
  const bingOriginResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/redirect-bing-origin", undefined, redirectHeaders);
  assert.equal(bingOriginResponse.status, 200);
  assert.equal(await bingOriginResponse.text(), "bing-origin-ok");
  assert.equal(redirectCredentials.sameSource, redirectAuthorization);
  assert.equal(redirectCredentials.sameTarget, redirectAuthorization);
  assert.equal(redirectCredentials.assetsSource, redirectAuthorization);
  assert.equal(redirectCredentials.assetsTarget, undefined);
  assert.equal(redirectCredentials.assetsHost, "assets.ion.cesium.com");
  assert.equal(redirectCredentials.bingSource, redirectAuthorization);
  assert.equal(redirectCredentials.bingTarget, undefined);
  assert.equal(redirectCredentials.bingHost, "dev.virtualearth.net");

  const held = Array.from({ length: 4 }, (_, index) => (
    rawProxyRequest(proxyPort, mapToken, `https://api.cesium.com/hold/${index}`).promise
  ));
  await withTimeout(holdStarted, 1_000, "first saturated request did not reach upstream");
  const queuedAbortRequest = rawProxyRequest(proxyPort, mapToken, "https://api.cesium.com/queued-abort");
  const saturatedStatus = await waitFor(async () => {
    const status = await (await fetch(`http://127.0.0.1:${proxyPort}/status`)).json();
    return status.transport.pool.queuedRequests >= 4 ? status : null;
  }, 1_000, "agent queue never reached saturation");
  assert.equal(saturatedStatus.transport.pool.maxQueuedRequests >= 4, true);
  queuedAbortRequest.abort();
  await assert.rejects(withTimeout(queuedAbortRequest.promise, 1_000, "queued abort did not reject client request"));
  await delay(100);
  holdReleased = true;
  releaseHold();
  const heldResults = await withTimeout(Promise.all(held), 2_000, "saturated requests did not drain");
  for (const [index, result] of heldResults.entries()) {
    assert.equal(result.status, 200);
    assert.equal(result.body, `hold-${index}`);
  }
  assert.equal(queuedAbortAttempts, 0);

  const redirectAbortRequest = rawProxyRequest(proxyPort, mapToken, "https://api.cesium.com/redirect-hold");
  await withTimeout(redirectSeen, 1_000, "redirect request did not reach upstream");
  redirectAbortRequest.abort();
  await assert.rejects(withTimeout(redirectAbortRequest.promise, 1_000, "redirect abort did not reject client request"));
  await withTimeout(redirectClosed, 1_000, "redirect abort did not close redirect body socket");

  const preHeaderRequest = rawProxyRequest(proxyPort, mapToken, "https://api.cesium.com/preheader");
  await withTimeout(preHeaderSeen, 1_000, "pre-header request did not reach upstream");
  preHeaderRequest.abort();
  await assert.rejects(withTimeout(preHeaderRequest.promise, 1_000, "pre-header abort did not reject client request"));
  await withTimeout(preHeaderClosed, 1_000, "pre-header abort did not close upstream socket");

  // With no pooled socket left, delay the next CONNECT response and abort the
  // browser-side request while that tunnel is still being established.
  nextConnectorDelayMs = 500;
  const connectAbortRequest = rawProxyRequest(proxyPort, mapToken, "https://api.cesium.com/connect-abort");
  await withTimeout(delayedConnectorSeen, 1_000, "CONNECT-abort request did not reach connector");
  connectAbortRequest.abort();
  await assert.rejects(withTimeout(connectAbortRequest.promise, 1_000, "CONNECT abort did not reject client request"));
  await withTimeout(delayedConnectorClosed, 1_000, "CONNECT abort did not close connector socket");
  await waitFor(async () => {
    const status = await (await fetch(`http://127.0.0.1:${proxyPort}/status`)).json();
    return status.transport.metrics.inFlightRequests === 0 ? status : null;
  }, 1_000, "CONNECT abort was not terminally accounted");

  // The abort above removed the pooled socket. A reset on the next freshly
  // opened socket must fail directly, never trigger the safe reused-socket retry.
  const nonReusedResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/non-reused-reset");
  assert.equal(nonReusedResponse.status, 502);
  assert.equal(nonReusedResetAttempts, 1);

  const warmResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/warm");
  assert.equal(warmResponse.status, 200);
  await warmResponse.text();

  const retryAbortRequest = rawProxyRequest(proxyPort, mapToken, "https://api.cesium.com/retry-abort");
  await withTimeout(retryAbortSecondSeen, 1_000, "retry abort did not reach its second attempt");
  retryAbortRequest.abort();
  await assert.rejects(withTimeout(retryAbortRequest.promise, 1_000, "retry abort did not reject client request"));
  await withTimeout(retryAbortSecondClosed, 1_000, "retry abort did not close second-attempt socket");
  assert.equal(retryAbortAttempts, 2);

  const retryLimitWarmResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/warm-after-retry-abort");
  assert.equal(retryLimitWarmResponse.status, 200);
  await retryLimitWarmResponse.text();

  // First failure is on a reused socket and is retried once. The second
  // failure is terminal: retry remains strictly bounded to two attempts.
  const retryLimitResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/retry-limit");
  assert.equal(retryLimitResponse.status, 502);
  assert.equal(retryLimitAttempts, 2);

  const idleResponse = await proxyFetch(proxyPort, mapToken, "https://api.cesium.com/idle");
  assert.equal(idleResponse.status, 200);
  await assert.rejects(withTimeout(idleResponse.text(), 2_000, "idle body timeout did not terminate downstream"));
  await withTimeout(idleClosed, 1_000, "idle timeout did not close upstream socket");

  const status = await (await fetch(`http://127.0.0.1:${proxyPort}/status`)).json();
  const metrics = status.transport.metrics;
  assert.equal(metrics.requests, 22);
  assert.equal(metrics.terminalRequests, 22);
  assert.equal(metrics.inFlightRequests, 0);
  assert.equal(metrics.completedResponses, 14);
  assert.equal(metrics.failedRequests, 3);
  assert.equal(metrics.retries, 3);
  assert.equal(metrics.streamFailures, 1);
  assert.equal(metrics.clientAborts, 5);
  assert.equal(metrics.responseBytes > 0, true);
  assert.equal(metrics.partialBytes >= Buffer.byteLength("partial"), true);
  assert.equal(metrics.reusedSocketRequests >= 8, true);
  assert.equal(metrics.timing.queueMs.max >= 100, true);
  assert.equal(metrics.timing.connectionMs.max > 0, true);
  assert.equal(metrics.timing.ttfbMs.max >= 20, true);
  assert.equal(metrics.timing.retryMs.max >= 25, true);
  assert.equal(status.transport.pool.queuedRequests, 0);
  assert.equal(status.transport.pool.maxQueuedRequests >= 4, true);
  assert.equal(status.transport.pool.maxSockets, 1);
  assert.equal(status.transport.metrics.byHost["api.cesium.com"].terminalRequests, 22);
  assert.equal(proxyOutput.includes('"event":"cesium_egress_reused_socket_retry"'), true);
  assert.equal(proxyOutput.includes('"event":"cesium_egress_retry_completed"'), true);
  assert.equal(proxyOutput.includes('"event":"cesium_egress_retry_failed"'), true);
  for (const secret of [connectorToken, mapToken, providerToken, redirectAuthorization]) assert.equal(proxyOutput.includes(secret), false);
  console.log("ok: aborts propagate, queue peaks are retained, retries are bounded, timings are separated, and terminal accounting is exact");
} catch (error) {
  if (proxyOutput) console.error(proxyOutput);
  throw error;
} finally {
  if (proxy && proxy.exitCode === null && proxy.signalCode === null) {
    proxy.kill("SIGTERM");
    try {
      await withTimeout(once(proxy, "exit"), 2_000, "proxy did not stop after SIGTERM");
    } catch {
      proxy.kill("SIGKILL");
      await withTimeout(once(proxy, "exit"), 2_000, "proxy did not stop after SIGKILL").catch(() => undefined);
    }
  }
  await closeServer(connector, connectorSockets);
  await closeServer(upstream, upstreamSockets);
  await rm(root, { recursive: true, force: true });
}

function proxyFetch(port, token, target, signal, extraHeaders = {}) {
  return fetch(`http://127.0.0.1:${port}/proxy/cesium/fetch?url=${encodeURIComponent(target)}`, {
    headers: { "x-proxy-token": token, ...extraHeaders },
    signal,
  });
}

function rawProxyRequest(port, token, target) {
  let request;
  const promise = new Promise((resolve, reject) => {
    request = createHttpRequest({
      host: "127.0.0.1",
      port,
      method: "GET",
      path: `/proxy/cesium/fetch?url=${encodeURIComponent(target)}`,
      headers: { "x-proxy-token": token },
      agent: false,
    }, (response) => {
      const chunks = [];
      response.on("data", (chunk) => chunks.push(chunk));
      response.once("end", () => resolve({ status: response.statusCode, body: Buffer.concat(chunks).toString("utf8") }));
      response.once("error", reject);
    });
    request.once("error", reject);
    request.end();
  });
  // Register a rejection observer immediately; the test asserts the same
  // original promise after it deliberately destroys the request.
  void promise.catch(() => undefined);
  return {
    promise,
    abort() { request.destroy(new Error("fixture_client_abort")); },
  };
}

async function freePort() {
  const server = createNetServer();
  server.listen(0, "127.0.0.1");
  await once(server, "listening");
  const address = server.address();
  assert(address && typeof address === "object");
  const closed = once(server, "close");
  server.close();
  await closed;
  return address.port;
}

async function waitForProxy(port, child, output) {
  for (let attempt = 0; attempt < 80; attempt += 1) {
    if (child.exitCode !== null) throw new Error(`proxy_exited:${child.exitCode}:${output()}`);
    try {
      const response = await fetch(`http://127.0.0.1:${port}/healthz`);
      if (response.ok) return;
    } catch { /* service is still starting */ }
    await delay(50);
  }
  throw new Error(`proxy_start_timeout:${output()}`);
}

async function waitFor(read, timeoutMs, message) {
  const startedAt = Date.now();
  while (Date.now() - startedAt < timeoutMs) {
    const value = await read();
    if (value) return value;
    await delay(20);
  }
  throw new Error(message);
}

function withTimeout(promise, timeoutMs, message) {
  let timeout;
  const deadline = new Promise((_, reject) => {
    timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
  });
  return Promise.race([promise, deadline]).finally(() => clearTimeout(timeout));
}

function delay(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

async function closeServer(server, sockets) {
  for (const socket of sockets) socket.destroy();
  if (!server?.listening) return;
  const closed = once(server, "close");
  server.close();
  await withTimeout(closed, 2_000, "fixture server did not close").catch(() => undefined);
}
