import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createServer } from "node:http";
import test from "node:test";

const bunCommand = process.env.HWLAB_TEST_BUN_COMMAND || process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");

test("Codex API Responses forwarder maps loopback Codex paths to configurable upstream and preserves stream body", async () => {
  const upstream = await startUpstream();
  const forwarder = await startForwarder(upstream.port, { upstreamPath: "/" });
  try {
    const response = await fetch(`http://127.0.0.1:${forwarder.port}/v1/responses`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer test-secret"
      },
      body: JSON.stringify({ model: "gpt-5.5", input: "OK" })
    });
    assert.equal(response.status, 200);
    const payload = await response.json();
    assert.equal(payload.ok, true);
    assert.equal(upstream.captured.url, "/responses");
    assert.equal(upstream.captured.authorization, "Bearer test-secret");
    assert.deepEqual(JSON.parse(upstream.captured.body), { model: "gpt-5.5", input: "OK" });

    const health = await fetch(`http://127.0.0.1:${forwarder.port}/health/live`).then((item) => item.json());
    assert.equal(health.upstream.host, "127.0.0.1");
    assert.equal(health.upstream.directNoProxyRequired, true);
  } finally {
    await forwarder.stop();
    await upstream.stop();
  }
});

test("Codex API Responses forwarder keeps /v1 prefix when upstream base is /v1", async () => {
  const upstream = await startUpstream();
  const forwarder = await startForwarder(upstream.port, { upstreamPath: "/v1" });
  try {
    const response = await fetch(`http://127.0.0.1:${forwarder.port}/responses`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ input: "OK" })
    });
    assert.equal(response.status, 200);
    assert.equal(upstream.captured.url, "/v1/responses");
  } finally {
    await forwarder.stop();
    await upstream.stop();
  }
});

async function startUpstream() {
  let captured = null;
  const server = createServer(async (request, response) => {
    const chunks = [];
    for await (const chunk of request) chunks.push(chunk);
    captured = {
      url: request.url,
      method: request.method,
      authorization: request.headers.authorization || null,
      body: Buffer.concat(chunks).toString("utf8")
    };
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true }));
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
  return {
    port: server.address().port,
    get captured() {
      return captured;
    },
    stop: () => new Promise((resolve) => server.close(resolve))
  };
}

async function startForwarder(upstreamPort, { upstreamPath }) {
  const port = await freePort();
  const child = spawn(bunCommand, ["run", "cmd/hwlab-codex-api-responses-forwarder/main.ts"], {
    cwd: process.cwd(),
    env: {
      ...process.env,
      HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT: String(port),
      HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL: `http://127.0.0.1:${upstreamPort}${upstreamPath}`
    },
    stdio: ["ignore", "pipe", "pipe"]
  });
  let stderr = "";
  child.stderr.on("data", (chunk) => {
    stderr += chunk;
  });
  await new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error(`forwarder did not start: ${stderr}`)), 5000);
    child.stdout.on("data", (chunk) => {
      if (chunk.toString().includes("listening")) {
        clearTimeout(timer);
        resolve();
      }
    });
    child.on("exit", (code) => {
      clearTimeout(timer);
      reject(new Error(`forwarder exited early code=${code}: ${stderr}`));
    });
  });
  return {
    port,
    stop: async () => {
      child.kill("SIGTERM");
      await new Promise((resolve) => child.once("exit", resolve));
    }
  };
}

async function freePort() {
  const server = createServer();
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
  const port = server.address().port;
  await new Promise((resolve) => server.close(resolve));
  return port;
}
