// If port exhaustion occurs, these tests fail.
// These tests fail by timing out.

import { expect, test } from "bun:test";
import { getMaxFD, isCI, isMacOS } from "harness";

// Since we bumped MAX_CONNECTIONS to 4, we should halve the threshold on macOS.
const PORT_EXHAUSTION_THRESHOLD = isMacOS ? 8 * 1024 : 16 * 1024;

async function runStressTest({
  onServerWritten,
  onFetchWritten,
}: {
  onServerWritten: (socket) => void;
  onFetchWritten: (socket) => void;
}) {
  const total = PORT_EXHAUSTION_THRESHOLD * 2;
  let sockets = [];
  const batch = 48;
  let toClose = 0;
  let pendingClose = Promise.withResolvers();
  const objects = [];
  for (let i = 0; i < total; i++) {
    objects.push({
      method: "POST",
      body: "--BYTEMARKER: " + (10 + i) + " ",
      keepalive: false,
    });
  }

  const server = await Bun.listen({
    port: 0,
    socket: {
      open(socket) {},
      data(socket, data) {
        const text = new TextDecoder().decode(data);
        const i = parseInt(text.slice(text.indexOf("--BYTEMARKER: ") + "--BYTEMARKER: ".length).slice(0, 3)) - 10;
        if (text.includes(objects[i].body)) {
          socket.data ??= {};
          socket.data.read = true;
          sockets[i] = socket;
          if (socket.write("200 OK\r\nCo") === "200 OK\r\nCo".length) {
            socket.data.written = true;
            onServerWritten(socket);
          }
          return;
        }

        console.log("Data is missing!");
      },
      drain(socket) {
        if (!socket.data?.read || socket.data?.written) {
          return;
        }

        if (socket.write("200 OK\r\nCo") === "200 OK\r\nCo".length) {
          socket.data.written = true;
          onServerWritten(socket);
        }
      },
      error(socket, err) {
        console.log(err);
      },
      timeout() {},
      close(socket) {
        toClose--;
        if (toClose === 0) {
          pendingClose.resolve();
        }
      },
    },
    hostname: "127.0.0.1",
  });
  let initialMaxFD = -1;
  for (let remaining = total; remaining > 0; remaining -= batch) {
    pendingClose = Promise.withResolvers();
    {
      const promises = [];
      toClose = batch;
      for (let i = 0; i < batch; i++) {
        promises.push(
          fetch(`http://127.0.0.1:${server.port}`, objects[i]).finally(() => {
            onFetchWritten(sockets[i]);
          }),
        );
      }
      await Promise.allSettled(promises);

      promises.length = 0;
    }

    await pendingClose.promise;
    if (total) sockets = [];

    if (initialMaxFD === -1) {
      initialMaxFD = getMaxFD();
    }
  }
  server.stop(true);
  await Bun.sleep(10);
  expect(getMaxFD()).toBeLessThan(initialMaxFD + 10);
}

test.todoIf(isCI && isMacOS)(
  "shutdown after timeout",
  async () => {
    await runStressTest({
      onServerWritten(socket) {
        socket.end();
      },
      onFetchWritten(socket) {},
    });
  },
  30 * 1000,
);

test.todoIf(isCI && isMacOS)(
  "close after TCP fin",
  async () => {
    await runStressTest({
      onServerWritten(socket) {
        socket.shutdown();
      },
      onFetchWritten(socket) {
        socket.end();
      },
    });
  },
  30 * 1000,
);

test.todoIf(isCI && isMacOS)(
  "shutdown then terminate",
  async () => {
    await runStressTest({
      onServerWritten(socket) {
        socket.shutdown();
      },
      onFetchWritten(socket) {
        socket.terminate();
      },
    });
  },
  30 * 1000,
);

test.todoIf(isCI && isMacOS)(
  "gently close",
  async () => {
    await runStressTest({
      onServerWritten(socket) {
        socket.end();
      },
      onFetchWritten(socket) {},
    });
  },
  30 * 1000,
);
