/**
 * Centralized error handling utilities for consistent error handling across services
 */
import {
  NotFoundException,
  BadRequestException,
  InternalServerErrorException,
  type Type,
} from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } from "./error.util.js";

/**
 * Types of exceptions that can be rethrown without wrapping
 */
type RethrowableException = Type<Error>;

/**
 * Options for error handling
 */
interface ErrorHandlerOptions {
  /**
   * Context string for logging (e.g., "Get invoices for user xyz")
   */
  context: string;

  /**
   * Exception types that should be rethrown as-is without wrapping
   * @default [NotFoundException, BadRequestException]
   */
  rethrow?: RethrowableException[];

  /**
   * Custom fallback message for InternalServerErrorException
   * @default `${context} failed`
   */
  fallbackMessage?: string;
}

/**
 * Wraps an async operation with consistent error handling.
 *
 * - Logs errors with context
 * - Rethrows specified exception types as-is
 * - Wraps other errors in InternalServerErrorException
 *
 * @example
 * return withErrorHandling(
 *   () => this.whmcsService.getInvoices(clientId, userId),
 *   this.logger,
 *   { context: `Get invoices for user ${userId}`, rethrow: [NotFoundException] }
 * );
 */
export async function withErrorHandling<T>(
  operation: () => Promise<T>,
  logger: Logger,
  options: ErrorHandlerOptions
): Promise<T> {
  const { context, rethrow = [NotFoundException, BadRequestException], fallbackMessage } = options;

  try {
    return await operation();
  } catch (error) {
    logger.error(context, { error: extractErrorMessage(error) });

    for (const ExceptionType of rethrow) {
      if (error instanceof ExceptionType) {
        throw error;
      }
    }

    throw new InternalServerErrorException(fallbackMessage ?? `${context} failed`);
  }
}

/**
 * Safe wrapper that returns null instead of throwing.
 * Useful for optional operations where failure is acceptable.
 *
 * @example
 * const cached = await withErrorSuppression(
 *   () => this.cache.get(key),
 *   this.logger,
 *   "Get cached value"
 * );
 */
export async function withErrorSuppression<T>(
  operation: () => Promise<T>,
  logger: Logger,
  context: string
): Promise<T | null> {
  try {
    return await operation();
  } catch (error) {
    logger.warn(`${context} (suppressed)`, { error: extractErrorMessage(error) });
    return null;
  }
}

/**
 * Wraps an async operation and logs errors, but always rethrows them.
 * Useful when you want consistent logging but don't want to change the error type.
 *
 * @example
 * return withErrorLogging(
 *   () => this.externalService.call(),
 *   this.logger,
 *   "Call external service"
 * );
 */
export async function withErrorLogging<T>(
  operation: () => Promise<T>,
  logger: Logger,
  context: string
): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    logger.error(context, { error: extractErrorMessage(error) });
    throw error;
  }
}
