import { sendProxyError } from "../common";
import type { RawResponseBodyHandler } from "./index";
import { decompressBuffer } from "./compression";

/**
 * Handles the response from the upstream service and decodes the body if
 * necessary. If the response is JSON, it will be parsed and returned as an
 * object. Otherwise, it will be returned as a string. Does not handle streaming
 * responses.
 * @throws {Error} Unsupported content-encoding or invalid application/json body
 */
export const handleBlockingResponse: RawResponseBodyHandler = async (
  proxyRes,
  req,
  res
) => {
  if (req.isStreaming) {
    const err = new Error(
      "handleBlockingResponse called for a streaming request."
    );
    req.log.error({ stack: err.stack, api: req.inboundApi }, err.message);
    throw err;
  }

  return new Promise((resolve, reject) => {
    let chunks: Buffer[] = [];
    proxyRes.on("data", (chunk) => chunks.push(chunk));
    proxyRes.on("end", async () => {
      const contentEncoding = proxyRes.headers["content-encoding"];
      const contentType = proxyRes.headers["content-type"];
      let body: string | Buffer = Buffer.concat(chunks);
      const rejectWithMessage = function (msg: string, err: Error) {
        const error = `${msg} (${err.message})`;
        req.log.warn(
          { msg: error, stack: err.stack },
          "Error in blocking response handler"
        );
        sendProxyError(req, res, 500, "Internal Server Error", { error });
        return reject(error);
      };

      try {
        body = await decompressBuffer(body, contentEncoding);
      } catch (e) {
        return rejectWithMessage(`Could not decode response body`, e);
      }

      try {
        return resolve(tryParseAsJson(body, contentType));
      } catch (e) {
        return rejectWithMessage("API responded with invalid JSON", e);
      }
    });
  });
};

function tryParseAsJson(body: string, contentType?: string) {
  // If the response is declared as JSON, it must parse or we will throw
  if (contentType?.includes("application/json")) {
    return JSON.parse(body);
  }
  // If it's not declared as JSON, some APIs we'll try to parse it as JSON
  // anyway since some APIs return the wrong content-type header in some cases.
  // If it fails to parse, we'll just return the raw body without throwing.
  try {
    return JSON.parse(body);
  } catch (e) {
    return body;
  }
}
