import { responseInterceptor } from 'http-proxy-middleware';

import type { IncomingMessage, ServerResponse } from 'node:http';

import type { IncomingRequest, JsonData, PluginLogger, ProxyListener } from './types.js';

type ResponseInterceptorCallback<TResponse, TResult> = (
  data: TResponse,
) => TResult | Promise<TResult>;

/**
 * Intercepts the API response and transforms it using the provided callback function.
 *
 * This function is designed to work with `http-proxy-middleware`'s `responseInterceptor`.
 * It allows you to modify the response data before it is sent to the client by applying
 * a transformation callback.
 *
 * **Note:** This function assumes that the response from the API is valid JSON. If the
 * response is not JSON, attempting to parse it will throw an error, causing the returned
 * promise to reject, which may result in the proxy sending an error response to the client.
 *
 * @template TResponse - The type of the original response data, which must be JSON-serializable.
 * @template TResult - The type of the transformed response data, which must also be JSON-serializable. Defaults to `TResponse`.
 *
 * @param callback - A function that takes the original response data of type `TResponse`
 * and returns the transformed data of type `TResult`.
 *
 * @returns A function that intercepts the response, applies the callback transformation,
 * and sends the transformed response to the client. This function is intended to be used
 * as the `onProxyRes` handler in `http-proxy-middleware`.
 *
 * @param proxyRes - The original response from the proxy server.
 * @param req - The original incoming request from the client.
 * @param res - The server response to be sent to the client.
 *
 * @example
 * ```typescript
 * import { apiResponseInterceptor } from './api-response-interceptor';
 *
 * type OriginalResponseType = { foo: string };
 * type TransformedResponseType = { bar: string };
 *
 * const transformResponse = (data: OriginalResponseType): TransformedResponseType => {
 *   return { bar: data.foo };
 * };
 *
 * const interceptor = apiResponseInterceptor(transformResponse);
 *
 * // Use the interceptor in your server setup
 * app.use('/api', createProxyMiddleware({
 *   target: 'http://example.com',
 *   changeOrigin: true,
 *   selfHandleResponse: true,
 *   onProxyRes: interceptor,
 * }));
 * ```
 */
export function createResponseInterceptor<
  TResponse extends JsonData,
  TResult extends JsonData = TResponse,
>(
  callback: ResponseInterceptorCallback<TResponse, TResult>,
  options?: { logger?: PluginLogger },
): ProxyListener {
  const { logger } = options ?? {};
  // Callback function for standard proxy handler
  return async (
    proxyRes: IncomingMessage,
    req: IncomingRequest,
    res: ServerResponse,
  ): Promise<void> => {
    // @ts-ignore
    logger?.debug(`intercepted response from ${req.originalUrl}`);

    const { headers, statusCode = 500 } = proxyRes;

    // Check if the response status code indicates an error
    // If the status code is 2xx, we can proceed with the transformation
    // If the status code is 4xx or 5xx, we should skip the transformation
    // and return the original response to the client
    if (statusCode >= 400) {
      proxyRes.pipe(res);
      return;
    }

    // Check if the response content type is JSON
    // If the content type is not JSON, we should skip the transformation
    // and return the original response to the client
    if (!headers['content-type']?.includes('application/json')) {
      logger?.debug('response is not JSON, skipping transformation');
      proxyRes.pipe(res);
      return;
    }

    // Apply the response interceptor
    const interceptor = responseInterceptor(async (responseBuffer): Promise<string> => {
      // Parse the response data and apply the callback transformation
      const response = JSON.parse(responseBuffer.toString()) as TResponse;

      // Apply the callback transformation
      const result = await Promise.resolve(callback(response));

      // Convert the transformed data to a JSON string
      return JSON.stringify(result);
    });

    // Return the transformed response
    await interceptor(proxyRes, req, res);
  };
}
