import { expect, test } from "bun:test";
import { nodeExe, tempDirWithFiles } from "harness";
import net from "node:net";

async function nodeRun(callback, clients = 1) {
  const cwd = tempDirWithFiles("server", {
    "index.mjs": `
  import net from "node:net";
  let clients = ${clients};
  const server = net.createServer({ allowHalfOpen: true }, socket => {
    // Listen for data from the client
    socket.on("data", data => {
      console.log(data.toString());
    });

    socket.on("end", () => {
      console.log("Received FIN");
      if(--clients == 0) {
        server.close();
      }
    });
    socket.on("error", console.error);

    // start sending FIN
    socket.end();
  });
  server.listen(0, "127.0.0.1", ()=> {
    console.log(server.address().port?.toString());
  })
  `,
  });
  const process = Bun.spawn([nodeExe(), "index.mjs"], {
    cwd,
    stdin: "ignore",
    stdout: "pipe",
    stderr: "pipe",
  });

  const reader = process.stdout.getReader();
  let continueReading = true;
  let stdout = "";
  let port = 0;
  do {
    const { done, value } = await reader.read();

    continueReading = !done;
    const decoder = new TextDecoder();
    if (value) {
      if (!port) {
        port = parseInt(decoder.decode(value), 10);
        callback(port);
      } else {
        stdout += decoder.decode(value);
      }
    }
  } while (continueReading);

  return {
    stdout,
    stderr: (await Bun.readableStreamToText(process.stderr)).trim(),
    code: await process.exited,
  };
}

async function doHalfOpenRequest(port, allowHalfOpen) {
  const { promise, resolve, reject } = Promise.withResolvers();

  const client = net.connect({ host: "127.0.0.1", port, allowHalfOpen }, () => {
    client.write("Hello, World");
  });
  client.on("error", reject);
  client.on("close", resolve);
  client.on("end", () => {
    // delay the write response
    setTimeout(() => {
      client.write("Write after end");
      client.end();
    }, 10);
  });
  await promise;
}

test("allowHalfOpen: true should work on client-side", async () => {
  const { promise: portPromise, resolve } = Promise.withResolvers();
  const process = nodeRun(resolve, 1);

  const port = await portPromise;
  await doHalfOpenRequest(port, true);
  const result = await process;
  expect(result.code).toBe(0);
  expect(result.stderr).toBe("");
  expect(
    result.stdout
      .split("\n")
      .map(s => s.trim())
      .filter(s => s),
  ).toEqual(["Hello, World", "Write after end", "Received FIN"]);
});

test("allowHalfOpen: false should work on client-side", async () => {
  const { promise: portPromise, resolve } = Promise.withResolvers();
  const process = nodeRun(resolve, 1);

  const port = await portPromise;
  await doHalfOpenRequest(port, false);
  const result = await process;
  expect(result.code).toBe(0);
  expect(result.stderr).toBe("");
  expect(
    result.stdout
      .split("\n")
      .map(s => s.trim())
      .filter(s => s),
  ).toEqual(["Hello, World", "Received FIN"]);
});
