/**
 * API Utility Functions
 * Unified API request and error handling with automatic token refresh
 * Automatically converts between snake_case (API) and camelCase (frontend)
 */

import { API_BASE_URL } from "@/core/config/api-config";
import { keysToCamel, keysToSnake } from "./case-converter";

// Flag to prevent multiple simultaneous refresh attempts
let isRefreshing = false;
let refreshPromise: Promise<boolean> | null = null;

/**
 * Get authentication token
 */
function getAuthToken(): string | null {
  return localStorage.getItem("access_token");
}

/**
 * Get refresh token
 */
function getRefreshToken(): string | null {
  return localStorage.getItem("refresh_token");
}

/**
 * Get authentication headers
 */
function getAuthHeaders(): HeadersInit {
  const token = getAuthToken();
  const headers: HeadersInit = {
    "Content-Type": "application/json",
  };
  if (token) {
    headers["Authorization"] = `Bearer ${token}`;
  }
  return headers;
}

/**
 * Refresh access token using refresh token
 */
async function refreshAccessToken(): Promise<boolean> {
  const refreshToken = getRefreshToken();
  if (!refreshToken) {
    return false;
  }

  try {
    const response = await fetch(`${API_BASE_URL}/v1/auth/refresh`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ refresh_token: refreshToken }),
    });

    if (!response.ok) {
      // Refresh token is invalid or expired
      localStorage.removeItem("access_token");
      localStorage.removeItem("refresh_token");
      return false;
    }

    const data = await response.json();
    localStorage.setItem("access_token", data.access_token);
    localStorage.setItem("refresh_token", data.refresh_token);
    return true;
  } catch (error) {
    console.error("Failed to refresh token:", error);
    return false;
  }
}

/**
 * API request options
 */
interface ApiRequestOptions extends Omit<RequestInit, "method" | "headers" | "body"> {
  method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
  body?: unknown;
  signal?: AbortSignal;  // Support request cancellation
  timeout?: number;  // Request timeout (milliseconds), default 30 seconds
  skipAuthRefresh?: boolean;  // Skip automatic token refresh
  skipCaseConversion?: boolean;  // Skip automatic snake_case ↔ camelCase conversion
}

/**
 * Unified API request function with automatic token refresh
 * @param endpoint - API endpoint path (e.g. /v1/cluster-configs)
 * @param options - Request options
 * @returns Promise<T> - Returns JSON data
 * @throws Error - Throws error on request failure
 */
export async function apiRequest<T = unknown>(
  endpoint: string,
  options: ApiRequestOptions = {}
): Promise<T> {
  const {
    method = "GET",
    body,
    signal,
    timeout = 30000,  // Default 30 second timeout
    skipAuthRefresh = false,
    skipCaseConversion = false,
    ...restOptions
  } = options;

  // Build complete URL
  const url = `${API_BASE_URL}${endpoint}`;

  // Convert request body from camelCase to snake_case
  const requestBody = body && !skipCaseConversion ? keysToSnake(body) : body;

  // Create timeout controller
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      method,
      headers: getAuthHeaders(),
      body: requestBody ? JSON.stringify(requestBody) : undefined,
      ...restOptions,
      signal: signal || controller.signal,
    });

    clearTimeout(timeoutId);

    // Handle 401 Unauthorized - try to refresh token
    if (response.status === 401 && !skipAuthRefresh) {
      // Wait if a refresh is already in progress
      if (isRefreshing && refreshPromise) {
        const refreshed = await refreshPromise;
        if (refreshed) {
          // Retry the original request with new token
          return apiRequest<T>(endpoint, { ...options, skipAuthRefresh: true });
        } else {
          // Refresh failed, redirect to login
          window.location.href = "/";
          throw new Error("Session expired, please login again");
        }
      }

      // Start token refresh
      isRefreshing = true;
      refreshPromise = refreshAccessToken();

      try {
        const refreshed = await refreshPromise;
        if (refreshed) {
          // Retry the original request with new token
          return apiRequest<T>(endpoint, { ...options, skipAuthRefresh: true });
        } else {
          // Refresh failed, redirect to login
          window.location.href = "/";
          throw new Error("Session expired, please login again");
        }
      } finally {
        isRefreshing = false;
        refreshPromise = null;
      }
    }

    if (!response.ok) {
      // Try to parse error message
      let errorMessage = `Request failed: ${response.status} ${response.statusText}`;

      try {
        const errorData = await response.json();
        // Try various error message formats
        if (errorData.error) {
          // OCI standard error format
          if (typeof errorData.error === "object" && errorData.error.message) {
            errorMessage = errorData.error.message;
          } else if (typeof errorData.error === "string") {
            errorMessage = errorData.error;
          }
        } else if (errorData.message) {
          errorMessage = errorData.message;
        } else if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
          errorMessage = errorData.errors[0].message || errorMessage;
        }
      } catch {
        // Unable to parse JSON, keep original error message
      }

      throw new Error(errorMessage);
    }

    // Handle empty response (e.g. DELETE request)
    const contentType = response.headers.get("content-type");
    if (!contentType || !contentType.includes("application/json")) {
      return undefined as T;
    }

    const responseData = await response.json();
    
    // Convert response from snake_case to camelCase
    return skipCaseConversion ? responseData : keysToCamel<T>(responseData);
  } catch (error) {
    clearTimeout(timeoutId);

    // Handle timeout error
    if (error instanceof Error && error.name === "AbortError") {
      throw new Error(`Request timeout: Server response time exceeded ${timeout / 1000} seconds`);
    }

    // Handle network error
    if (error instanceof TypeError) {
      throw new Error('Network connection failed: Please check network connection or backend service status');
    }

    throw error;
  }
}
