/**
 * API Response Helpers for FitAI Mobile App
 *
 * Utilities for handling API responses on the client side.
 */

import {
  ApiResponse,
  ApiErrorResponse,
  ApiSuccessResponse,
  isSuccessResponse,
  isErrorResponse,
  ApiErrorCode,
} from "./types";

/**
 * Error thrown when API request fails
 */
export class ApiError extends Error {
  constructor(
    message: string,
    public code?: string,
    public details?: Record<string, string[]>,
    public status?: number,
  ) {
    super(message);
    this.name = "ApiError";
  }

  /**
   * Create ApiError from error response
   */
  static fromResponse(response: ApiErrorResponse, status?: number): ApiError {
    return new ApiError(
      response.error.message,
      response.error.code,
      response.error.details,
      status,
    );
  }

  /**
   * Check if error is a specific error code
   */
  isCode(code: ApiErrorCode | string): boolean {
    return this.code === code;
  }

  /**
   * Get user-friendly error message
   */
  getUserMessage(): string {
    // Map error codes to user-friendly messages
    const userMessages: Record<string, string> = {
      [ApiErrorCode.UNAUTHORIZED]: "Please sign in to continue",
      [ApiErrorCode.FORBIDDEN]: "You don't have permission to do that",
      [ApiErrorCode.NOT_FOUND]: "Item not found",
      [ApiErrorCode.VALIDATION_ERROR]: "Please check your input and try again",
      [ApiErrorCode.NETWORK_ERROR]:
        "Connection failed. Please check your internet.",
      [ApiErrorCode.TIMEOUT]: "Request timed out. Please try again.",
      [ApiErrorCode.INTERNAL_ERROR]:
        "Something went wrong. Please try again later.",
    };

    if (this.code && userMessages[this.code]) {
      return userMessages[this.code];
    }

    return this.message;
  }

  /**
   * Get first validation error message
   */
  getFirstValidationError(): string | undefined {
    if (!this.details) return undefined;

    for (const field in this.details) {
      const errors = this.details[field];
      if (errors && errors.length > 0) {
        return errors[0];
      }
    }

    return undefined;
  }
}

/**
 * Handle API response and throw error if not successful
 *
 * @param response - API response to handle
 * @returns Data from successful response
 * @throws ApiError if response is not successful
 *
 * @example
 * const data = handleResponse(await fetch('/api/users').then(r => r.json()));
 */
export function handleResponse<T>(
  response: ApiResponse<T>,
  status?: number,
): T {
  if (isSuccessResponse(response)) {
    return response.data;
  }

  throw ApiError.fromResponse(response, status);
}

/**
 * Safely handle API response without throwing
 *
 * @param response - API response to handle
 * @returns Result object with data or error
 *
 * @example
 * const result = safeHandleResponse(await fetch('/api/users').then(r => r.json()));
 * if (result.success) {
 *   console.log(result.data);
 * } else {
 *   console.error(result.error);
 * }
 */
export function safeHandleResponse<T>(
  response: ApiResponse<T>,
  status?: number,
): { success: true; data: T } | { success: false; error: ApiError } {
  if (isSuccessResponse(response)) {
    return { success: true, data: response.data };
  }

  return {
    success: false,
    error: ApiError.fromResponse(response, status),
  };
}

/**
 * Extract error message from unknown error
 *
 * @param error - Error to extract message from
 * @returns Error message string
 */
export function getErrorMessage(error: unknown): string {
  if (error instanceof ApiError) {
    return error.getUserMessage();
  }

  if (error instanceof Error) {
    return error.message;
  }

  if (typeof error === "string") {
    return error;
  }

  return "An unexpected error occurred";
}

/**
 * Check if error is network related
 */
export function isNetworkError(error: unknown): boolean {
  if (error instanceof ApiError) {
    return (
      error.code === ApiErrorCode.NETWORK_ERROR ||
      error.code === ApiErrorCode.TIMEOUT
    );
  }

  if (error instanceof Error) {
    return (
      error.message.includes("network") ||
      error.message.includes("fetch") ||
      error.message.includes("timeout")
    );
  }

  return false;
}

/**
 * Check if error is authentication related
 */
export function isAuthError(error: unknown): boolean {
  if (error instanceof ApiError) {
    return (
      error.code === ApiErrorCode.UNAUTHORIZED ||
      error.code === ApiErrorCode.INVALID_TOKEN ||
      error.status === 401
    );
  }

  return false;
}

/**
 * Check if error is permission related
 */
export function isPermissionError(error: unknown): boolean {
  if (error instanceof ApiError) {
    return (
      error.code === ApiErrorCode.FORBIDDEN ||
      error.code === ApiErrorCode.INSUFFICIENT_PERMISSIONS ||
      error.status === 403
    );
  }

  return false;
}

/**
 * Retry an API call with exponential backoff
 *
 * @param fn - Async function to retry
 * @param options - Retry configuration
 * @returns Result of successful call
 *
 * @example
 * const data = await retryApiCall(
 *   () => fetch('/api/users').then(r => r.json()),
 *   { maxRetries: 3, initialDelay: 1000 }
 * );
 */
export async function retryApiCall<T>(
  fn: () => Promise<T>,
  options?: {
    maxRetries?: number;
    initialDelay?: number;
    maxDelay?: number;
    shouldRetry?: (error: unknown) => boolean;
  },
): Promise<T> {
  const maxRetries = options?.maxRetries ?? 3;
  const initialDelay = options?.initialDelay ?? 1000;
  const maxDelay = options?.maxDelay ?? 10000;
  const shouldRetry =
    options?.shouldRetry ??
    ((error: unknown) => {
      // Retry on network errors and 5xx errors
      if (isNetworkError(error)) return true;
      if (error instanceof ApiError && error.status && error.status >= 500) {
        return true;
      }
      return false;
    });

  let lastError: unknown;
  let delay = initialDelay;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Don't retry if this was the last attempt or if error is not retryable
      if (attempt === maxRetries || !shouldRetry(error)) {
        throw error;
      }

      // Wait before retrying
      await new Promise((resolve) => setTimeout(resolve, delay));

      // Exponential backoff with max delay cap
      delay = Math.min(delay * 2, maxDelay);
    }
  }

  throw lastError;
}

/**
 * Create a type-safe API response wrapper
 *
 * @param data - Response data
 * @returns Standardized success response
 */
export function createSuccessResponse<T>(data: T): ApiSuccessResponse<T> {
  return {
    success: true,
    data,
    meta: {
      timestamp: new Date().toISOString(),
    },
  };
}

/**
 * Create a type-safe error response
 *
 * @param message - Error message
 * @param code - Error code
 * @param details - Additional error details
 * @returns Standardized error response
 */
export function createErrorResponse(
  message: string,
  code?: string,
  details?: Record<string, string[]>,
): ApiErrorResponse {
  return {
    success: false,
    error: {
      message,
      code,
      details,
    },
    meta: {
      timestamp: new Date().toISOString(),
    },
  };
}
