// Copyright 2021-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import * as zlib from "zlib";
import { promisify } from "util";
import type { Compression } from "@apachedubbo/dubbo/protocol";
import { Code, DubboError } from "@apachedubbo/dubbo";
import { getNodeErrorProps } from "./node-error.js";

const gzip = promisify(zlib.gzip);
const gunzip = promisify(zlib.gunzip);
const brotliCompress = promisify(zlib.brotliCompress);
const brotliDecompress = promisify(zlib.brotliDecompress);

/**
 * The gzip compression algorithm, implemented with the Node.js built-in module
 * zlib. This value can be used for the `sendCompression` and `acceptCompression`
 * option of client transports, or for the `acceptCompression` option of server
 * plugins like `fastifyDubboPlugin` from @apachedubbo/dubbo-fastify.
 */
export const compressionGzip: Compression = {
  name: "gzip",
  compress(bytes) {
    return gzip(bytes, {});
  },
  decompress(bytes, readMaxBytes) {
    return wrapZLibErrors(
      gunzip(bytes, {
        maxOutputLength: readMaxBytes,
      }),
      readMaxBytes
    );
  },
};

/**
 * The brotli compression algorithm, implemented with the Node.js built-in module
 * zlib. This value can be used for the `sendCompression` and `acceptCompression`
 * option of client transports, or for the `acceptCompression` option of server
 * plugins like `fastifyDubboPlugin` from @apachedubbo/dubbo-fastify.
 */
export const compressionBrotli: Compression = {
  name: "br",
  compress(bytes) {
    return brotliCompress(bytes, {});
  },
  decompress(bytes, readMaxBytes) {
    return wrapZLibErrors(
      brotliDecompress(bytes, {
        maxOutputLength: readMaxBytes,
      }),
      readMaxBytes
    );
  },
};

function wrapZLibErrors<T>(
  promise: Promise<T>,
  readMaxBytes: number
): Promise<T> {
  return promise.catch((e) => {
    const { code } = getNodeErrorProps(e);
    // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
    switch (code) {
      case "ERR_BUFFER_TOO_LARGE":
        e = new DubboError(
          `message is larger than configured readMaxBytes ${readMaxBytes} after decompression`,
          Code.ResourceExhausted
        );
        break;
      case "Z_DATA_ERROR":
      case "ERR_PADDING_2":
        e = new DubboError(
          "decompression failed",
          Code.InvalidArgument,
          undefined,
          undefined,
          e
        );
        break;
      default:
        e = new DubboError(
          "decompression failed",
          Code.Internal,
          undefined,
          undefined,
          e
        );
        break;
    }
    return Promise.reject(e);
  });
}
