import { createServer, request as httpRequest } from "node:http";
import { request as httpsRequest } from "node:https";

export class HttpError extends Error {
  constructor(statusCode, body) {
    super(body?.message ?? body?.error ?? `HTTP ${statusCode}`);
    this.statusCode = statusCode;
    this.body = body;
  }
}

export function jsonResponse(res, statusCode, body) {
  const payload = JSON.stringify(body, null, 2);
  res.writeHead(statusCode, {
    "content-type": "application/json; charset=utf-8",
    "content-length": Buffer.byteLength(payload)
  });
  res.end(payload);
}

export async function readJson(req) {
  const chunks = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }

  const raw = Buffer.concat(chunks).toString("utf8").trim();
  if (!raw) {
    return {};
  }
  return JSON.parse(raw);
}

export function createJsonServer({ routes }) {
  return createServer(async (req, res) => {
    try {
      const url = new URL(req.url ?? "/", "http://localhost");
      const key = `${req.method ?? "GET"} ${url.pathname}`;
      const handler = routes.get(key);

      if (!handler) {
        jsonResponse(res, 404, {
          error: "not_found",
          path: url.pathname
        });
        return;
      }

      const body = await handler({ req, url });
      jsonResponse(res, 200, body);
    } catch (error) {
      if (error instanceof HttpError) {
        jsonResponse(res, error.statusCode, error.body);
        return;
      }

      if (Number.isInteger(error?.statusCode) && error?.body) {
        jsonResponse(res, error.statusCode, error.body);
        return;
      }

      if (error instanceof SyntaxError) {
        jsonResponse(res, 400, {
          error: "invalid_json",
          message: error.message
        });
        return;
      }

      jsonResponse(res, 500, {
        error: "internal_error",
        message: error instanceof Error ? error.message : String(error)
      });
    }
  });
}

export async function fetchJson(url, options = {}) {
  const body = options.body === undefined ? undefined : JSON.stringify(options.body);
  const target = url instanceof URL ? url : new URL(String(url));
  const client = target.protocol === "https:" ? httpsRequest : httpRequest;
  const headers = {
    ...(body === undefined
      ? {}
      : {
          "content-type": "application/json",
          "content-length": Buffer.byteLength(body)
        }),
    ...(options.headers ?? {})
  };
  return new Promise((resolve, reject) => {
    const request = client(target, {
      method: options.method ?? (body === undefined ? "GET" : "POST"),
      headers,
      signal: options.signal
    }, (response) => {
      let text = "";
      response.setEncoding("utf8");
      response.on("data", (chunk) => {
        text += chunk;
      });
      response.on("end", () => {
        try {
          const json = text ? JSON.parse(text) : null;
          const status = response.statusCode ?? 0;
          resolve({
            ok: status >= 200 && status < 300,
            status,
            statusText: response.statusMessage ?? "",
            body: json
          });
        } catch (error) {
          reject(error);
        }
      });
    });
    request.on("error", reject);
    if (body !== undefined) request.write(body);
    request.end();
  });
}

export function parsePort(value, fallback) {
  const parsed = Number.parseInt(value ?? "", 10);
  if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) {
    return parsed;
  }
  return fallback;
}

export function listen(server, port, host = "0.0.0.0") {
  server.listen(port, host, () => {
    const address = server.address();
    const bound = typeof address === "object" && address ? `${address.address}:${address.port}` : String(address);
    console.log(`listening on ${bound}`);
  });
}
