import { Injectable } from "@nestjs/common";
import { DomainHttpException } from "@bff/core/http/domain-http.exception.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { matchCommonError } from "@bff/core/errors/index.js";
import { FreebitError } from "./freebit-error.service.js";
import {
  BaseProviderError,
  FreebitApiError,
  FreebitAccountNotFoundError,
  FreebitTimeoutError,
  FreebitAuthError,
  FreebitRateLimitError,
  FreebitValidationError,
  FreebitNetworkError,
} from "@bff/integrations/common/errors/index.js";

/**
 * Service for handling and normalizing Freebit API errors.
 * Maps Freebit errors to typed provider error classes.
 *
 * Mirrors the pattern used by WhmcsErrorHandlerService and SalesforceErrorHandlerService.
 */
@Injectable()
export class FreebitErrorHandlerService {
  /**
   * Handle Freebit API error (from FreebitError or raw error)
   */
  handleApiError(error: unknown, context: string): never {
    if (error instanceof FreebitError) {
      this.throwTypedFreebitError(error);
    }

    // Handle generic errors
    this.handleRequestError(error, context);
  }

  /**
   * Handle general request errors (network, timeout, etc.)
   */
  handleRequestError(error: unknown, context: string): never {
    const message = extractErrorMessage(error);

    // Check for auth errors (FreebitError-aware)
    if (error instanceof FreebitError && error.isAuthError?.()) {
      throw new FreebitAuthError(error.message, error);
    }
    if (this.isAuthError(message)) {
      throw new FreebitAuthError(message, error);
    }

    // Check for rate limiting (FreebitError-aware)
    if (error instanceof FreebitError && error.isRateLimitError?.()) {
      throw new FreebitRateLimitError(error.message, error);
    }
    if (this.isRateLimitError(message)) {
      throw new FreebitRateLimitError(message, error);
    }

    // Check for timeout
    if (this.isTimeoutError(message)) {
      throw new FreebitTimeoutError(message, error);
    }

    // Check for network errors
    if (this.isNetworkError(message)) {
      throw new FreebitNetworkError(message, error);
    }

    // Re-throw if already a BaseProviderError or DomainHttpException
    if (error instanceof BaseProviderError) {
      throw error;
    }
    if (error instanceof DomainHttpException) {
      throw error;
    }

    // Wrap unknown errors
    throw new FreebitApiError(`Freebit ${context} failed`, message, error);
  }

  /**
   * Map FreebitError to typed provider error and throw
   */
  private throwTypedFreebitError(error: FreebitError): never {
    const resultCode = String(error.resultCode || "");
    const statusCode = String(error.statusCode || "");
    const message = error.message.toLowerCase();

    // Authentication errors
    if (error.isAuthError?.()) {
      throw new FreebitAuthError(error.message, error);
    }

    // Rate limit errors
    if (error.isRateLimitError?.()) {
      throw new FreebitRateLimitError(error.message, error);
    }

    // Not found errors
    if (message.includes("account not found") || message.includes("no such account")) {
      throw new FreebitAccountNotFoundError(error.message, error);
    }

    // Validation result codes (215 = plan change, 381/382 = network type change)
    if (
      resultCode === "215" ||
      statusCode === "215" ||
      resultCode === "381" ||
      statusCode === "381" ||
      resultCode === "382" ||
      statusCode === "382"
    ) {
      throw new FreebitValidationError(error.message, error);
    }

    // Timeout
    if (this.isTimeoutError(error.message)) {
      throw new FreebitTimeoutError(error.message, error);
    }

    // Retryable server errors
    if (error.isRetryable?.()) {
      throw new FreebitApiError("Freebit server error", error.message, error);
    }

    // Default: generic Freebit API error
    throw new FreebitApiError("Freebit API error", error.message, error);
  }

  /**
   * Check if message indicates timeout
   */
  private isTimeoutError(message: string): boolean {
    const lowerMessage = message.toLowerCase();
    return lowerMessage.includes("timeout") || lowerMessage.includes("aborted");
  }

  /**
   * Check if message indicates network error
   */
  private isNetworkError(message: string): boolean {
    const lowerMessage = message.toLowerCase();
    return (
      lowerMessage.includes("network") ||
      lowerMessage.includes("connection") ||
      lowerMessage.includes("econnrefused") ||
      lowerMessage.includes("fetch failed")
    );
  }

  /**
   * Check if message indicates rate limit
   */
  private isRateLimitError(message: string): boolean {
    const lowerMessage = message.toLowerCase();
    return (
      lowerMessage.includes("rate limit") ||
      lowerMessage.includes("too many requests") ||
      lowerMessage.includes("429")
    );
  }

  /**
   * Check if message indicates auth error
   */
  private isAuthError(message: string): boolean {
    const lowerMessage = message.toLowerCase();
    return (
      lowerMessage.includes("authentication") ||
      lowerMessage.includes("unauthorized") ||
      lowerMessage.includes("401")
    );
  }

  /**
   * Get user-friendly error message for Freebit errors
   */
  getUserFriendlyMessage(error: unknown): string {
    // Use FreebitError's built-in method if available
    if (error instanceof FreebitError) {
      return error.getUserFriendlyMessage();
    }

    // Fall back to common error patterns
    const message = extractErrorMessage(error);
    const commonResult = matchCommonError(message, "sim");

    if (commonResult.matched) {
      return commonResult.message;
    }

    return "SIM operation failed. Please try again or contact support.";
  }
}
