import type { IncomingMessage } from 'http';

export interface ReadRequestBodyOptions {
  maxBytes: number;
}

export class RequestBodyTooLargeError extends Error {
  readonly maxBytes: number;
  readonly receivedBytes: number;

  constructor(maxBytes: number, receivedBytes: number) {
    super(`Request body too large (${receivedBytes} bytes > ${maxBytes} bytes)`);
    this.name = 'RequestBodyTooLargeError';
    this.maxBytes = maxBytes;
    this.receivedBytes = receivedBytes;
  }
}

/** Read the full request body with an explicit max-size limit. */
export function readRequestBody(req: IncomingMessage, opts: ReadRequestBodyOptions): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    let totalBytes = 0;
    let settled = false;

    const cleanup = () => {
      req.off('data', onData);
      req.off('end', onEnd);
      req.off('error', onError);
    };

    const fail = (err: Error) => {
      if (settled) {return;}
      settled = true;
      cleanup();
      reject(err);
    };

    const onData = (chunk: Buffer | string) => {
      const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      totalBytes += buf.length;
      if (totalBytes > opts.maxBytes) {
        if (typeof req.destroy === 'function') {
          req.destroy();
        }
        fail(new RequestBodyTooLargeError(opts.maxBytes, totalBytes));
        return;
      }
      chunks.push(buf);
    };

    const onEnd = () => {
      if (settled) {return;}
      settled = true;
      cleanup();
      resolve(Buffer.concat(chunks).toString('utf-8'));
    };

    const onError = (err: Error) => fail(err);

    req.on('data', onData);
    req.on('end', onEnd);
    req.on('error', onError);
  });
}
