import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
import { Readable } from "node:stream";
import type { ApiEnvironment } from "./environment.js";
import type { ApiHandler } from "./composition.js";

function requestUrl(request: IncomingMessage): URL {
  const host = request.headers.host ?? "localhost";
  return new URL(request.url ?? "/", `http://${host}`);
}

function toWebRequest(request: IncomingMessage): Request {
  const method = request.method ?? "GET";
  const hasBody = method !== "GET" && method !== "HEAD";
  return new Request(requestUrl(request), {
    method,
    headers: new Headers(request.headers as Record<string, string>),
    ...(hasBody ? { body: Readable.toWeb(request) as ReadableStream, duplex: "half" } : {}),
  });
}

async function send(response: Response, target: ServerResponse): Promise<void> {
  target.statusCode = response.status;
  response.headers.forEach((value, key) => target.setHeader(key, value));
  if (response.body === null) {
    target.end();
    return;
  }
  for await (const chunk of Readable.fromWeb(response.body)) target.write(chunk);
  target.end();
}

export function createNodeServer(handler: ApiHandler): Server {
  return createServer((request, response) => {
    void handler(toWebRequest(request)).then(
      (result) => send(result, response),
      () => send(Response.json({ code: "INTERNAL_ERROR" }, { status: 500 }), response),
    );
  });
}

export function listen(server: Server, environment: ApiEnvironment): Promise<Server> {
  return new Promise((resolve, reject) => {
    const onError = (error: Error): void => reject(error);
    server.once("error", onError);
    server.listen(environment.port, environment.host, () => {
      server.off("error", onError);
      resolve(server);
    });
  });
}
