import { Injectable, HttpStatus } from "@nestjs/common";
import { ErrorCode, type ErrorCodeType } from "@customer-portal/domain/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";

/**
 * Service for handling and normalizing Freebit API errors.
 * Maps Freebit errors to appropriate NestJS exceptions.
 *
 * 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) {
      const mapped = this.mapFreebitErrorToDomain(error);
      throw new DomainHttpException(mapped.code, mapped.status, error.message);
    }

    // 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 timeout
    if (this.isTimeoutError(message)) {
      throw new DomainHttpException(ErrorCode.TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
    }

    // Check for network errors
    if (this.isNetworkError(message)) {
      throw new DomainHttpException(ErrorCode.NETWORK_ERROR, HttpStatus.BAD_GATEWAY);
    }

    // Check for rate limiting
    if (this.isRateLimitError(message)) {
      throw new DomainHttpException(
        ErrorCode.RATE_LIMITED,
        HttpStatus.TOO_MANY_REQUESTS,
        "Freebit rate limit exceeded"
      );
    }

    // Check for auth errors
    if (this.isAuthError(message)) {
      throw new DomainHttpException(
        ErrorCode.EXTERNAL_SERVICE_ERROR,
        HttpStatus.SERVICE_UNAVAILABLE,
        "Freebit authentication failed"
      );
    }

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

    // Wrap unknown errors
    throw new DomainHttpException(
      ErrorCode.EXTERNAL_SERVICE_ERROR,
      HttpStatus.BAD_GATEWAY,
      `Freebit ${context} failed`
    );
  }

  /**
   * Map FreebitError to domain error codes
   */
  private mapFreebitErrorToDomain(error: FreebitError): {
    code: ErrorCodeType;
    status: HttpStatus;
  } {
    const resultCode = String(error.resultCode || "");
    const statusCode = String(error.statusCode || "");
    const message = error.message.toLowerCase();

    // Authentication errors
    if (error.isAuthError()) {
      return { code: ErrorCode.EXTERNAL_SERVICE_ERROR, status: HttpStatus.SERVICE_UNAVAILABLE };
    }

    // Rate limit errors
    if (error.isRateLimitError()) {
      return { code: ErrorCode.RATE_LIMITED, status: HttpStatus.TOO_MANY_REQUESTS };
    }

    // Not found errors
    if (message.includes("account not found") || message.includes("no such account")) {
      return { code: ErrorCode.NOT_FOUND, status: HttpStatus.NOT_FOUND };
    }

    // Plan change specific errors
    if (resultCode === "215" || statusCode === "215") {
      return { code: ErrorCode.VALIDATION_FAILED, status: HttpStatus.BAD_REQUEST };
    }

    // Network type change errors
    if (
      resultCode === "381" ||
      statusCode === "381" ||
      resultCode === "382" ||
      statusCode === "382"
    ) {
      return { code: ErrorCode.VALIDATION_FAILED, status: HttpStatus.BAD_REQUEST };
    }

    // Server errors (retryable)
    if (error.isRetryable() && !this.isTimeoutError(error.message)) {
      return { code: ErrorCode.EXTERNAL_SERVICE_ERROR, status: HttpStatus.BAD_GATEWAY };
    }

    // Timeout
    if (this.isTimeoutError(error.message)) {
      return { code: ErrorCode.TIMEOUT, status: HttpStatus.GATEWAY_TIMEOUT };
    }

    // Default: external service error
    return { code: ErrorCode.EXTERNAL_SERVICE_ERROR, status: HttpStatus.BAD_GATEWAY };
  }

  /**
   * 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.";
  }
}
