// src/services/apiClient.ts
import { Profile, ShoppingListItem, SearchQuery, Budget, Address } from '../types';
import { logger } from './logger.client';
import { eventBus } from './eventBus';

// This constant should point to your backend API.
// It's often a good practice to store this in an environment variable.
// Using a relative path '/api' is the most robust method for production.
// It makes API calls to the same host that served the frontend files,
// which is then handled by the Nginx reverse proxy.
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';

export interface ApiOptions {
  tokenOverride?: string;
  signal?: AbortSignal;
}

// --- API Fetch Wrapper with Token Refresh Logic ---

/**
 * Helper to safely join a base URL and a path, ensuring exactly one slash between them.
 * This preserves path segments in the base URL (e.g. '/api') which `new URL()` would strip
 * if the path argument started with a slash.
 */
const joinUrl = (base: string, path: string): string => {
  const cleanBase = base.replace(/\/+$/, '');
  const cleanPath = path.replace(/^\/+/, '');
  return `${cleanBase}/${cleanPath}`;
};

/**
 * A promise that holds the in-progress token refresh operation.
 * This prevents multiple parallel refresh requests.
 */
let refreshTokenPromise: Promise<string> | null = null;

/**
 * Attempts to refresh the access token using the HttpOnly refresh token cookie.
 * @returns A promise that resolves to the new access token.
 */
const refreshToken = async (): Promise<string> => {
  logger.info('Attempting to refresh access token...');
  try {
    // Use the joinUrl helper for consistency, though usually this is a relative fetch in browser
    const refreshUrl =
      typeof window === 'undefined' && !API_BASE_URL.startsWith('http')
        ? joinUrl(API_BASE_URL, '/auth/refresh-token')
        : `${API_BASE_URL}/auth/refresh-token`;

    const response = await fetch(refreshUrl, {
      method: 'POST',
      // This endpoint relies on the HttpOnly cookie, so no body is needed.
      headers: { 'Content-Type': 'application/json' },
    });
    const data = await response.json();
    if (!response.ok) {
      throw new Error(data.message || 'Failed to refresh token.');
    }
    // On successful refresh, store the new access token.
    if (typeof window !== 'undefined') {
      localStorage.setItem('authToken', data.token);
    }
    logger.info('Successfully refreshed access token.');
    return data.token;
  } catch (error) {
    logger.error({ error }, 'Failed to refresh token. User session has expired.');
    // Only perform browser-specific actions if in the browser environment.
    if (typeof window !== 'undefined') {
      localStorage.removeItem('authToken');
      // Dispatch a global event that the UI layer can listen for to handle session expiry.
      eventBus.dispatch('sessionExpired');
    }
    throw error;
  }
};

/**
 * A custom fetch wrapper that handles automatic token refreshing.
 * All authenticated API calls should use this function.
 * @param url The URL to fetch.
 * @param options The fetch options.
 * @returns A promise that resolves to the fetch Response.
 */
export const apiFetch = async (
  url: string,
  options: RequestInit = {},
  apiOptions: ApiOptions = {},
): Promise<Response> => {
  // Always construct the full URL from the base and the provided path,
  // unless the path is already a full URL. This works for both browser and Node.js.
  const fullUrl = url.startsWith('http') ? url : joinUrl(API_BASE_URL, url);

  logger.debug(`apiFetch: ${options.method || 'GET'} ${fullUrl}`);

  // Create a new headers object to avoid mutating the original options.
  const headers = new Headers(options.headers || {});
  // Use the token override if provided (for testing), otherwise get it from localStorage.
  // The `typeof window` check prevents errors in the Node.js test environment.
  const token =
    apiOptions.tokenOverride ??
    (typeof window !== 'undefined' ? localStorage.getItem('authToken') : null);
  if (token) {
    headers.set('Authorization', `Bearer ${token}`);
  }

  // Do not set Content-Type for FormData. The browser must set it with the
  // correct multipart boundary. For all other requests, default to application/json.
  if (!(options.body instanceof FormData) && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }

  const newOptions = { ...options, headers, signal: apiOptions.signal || options.signal };

  let response = await fetch(fullUrl, newOptions);

  if (response.status === 401) {
    // Prevent an infinite loop if the refresh token endpoint itself returns 401.
    if (fullUrl.includes('/auth/refresh-token')) {
      return response;
    }

    try {
      logger.info(`apiFetch: Received 401 for ${fullUrl}. Attempting token refresh.`);
      // If no refresh is in progress, start one.
      if (!refreshTokenPromise) {
        refreshTokenPromise = refreshToken();
      }

      // Wait for the existing or new refresh operation to complete.
      const newToken = await refreshTokenPromise;

      logger.info(`apiFetch: Token refreshed. Retrying original request to ${fullUrl}.`);
      // Retry the original request with the new token.
      headers.set('Authorization', `Bearer ${newToken}`);
      response = await fetch(fullUrl, { ...options, headers });
    } catch (refreshError) {
      // If refreshToken() fails, it will redirect, but we re-throw just in case.
      return Promise.reject(refreshError);
    } finally {
      // Clear the promise so the next 401 will trigger a new refresh.
      refreshTokenPromise = null;
    }
  }

  // --- DEBUG LOGGING for failed requests ---
  if (!response.ok) {
    const responseText = await response.clone().text();
    logger.error({ url: fullUrl, status: response.status, body: responseText },
      'apiFetch: Request failed',
    );
  }
  // --- END DEBUG LOGGING ---

  return response;
};

// --- API Helper Functions ---

/** Helper for public GET requests */
export const publicGet = (endpoint: string): Promise<Response> => {
  return fetch(`${API_BASE_URL}${endpoint}`);
};

/** Helper for public POST requests with a JSON body */
export const publicPost = <T>(endpoint: string, body: T): Promise<Response> => {
  return fetch(`${API_BASE_URL}${endpoint}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
};

/** Helper for authenticated GET requests */
export const authedGet = (endpoint: string, options: ApiOptions = {}): Promise<Response> => {
  return apiFetch(endpoint, { method: 'GET' }, options);
};

/** Helper for authenticated POST requests with a JSON body */
export const authedPost = <T>(endpoint: string, body: T, options: ApiOptions = {}): Promise<Response> => {
  return apiFetch(
    endpoint,
    { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },
    options,
  );
};

/** Helper for authenticated POST requests with an empty body */
export const authedPostEmpty = (endpoint: string, options: ApiOptions = {}): Promise<Response> => {
  return apiFetch(endpoint, { method: 'POST' }, options);
};

/** Helper for authenticated POST requests with FormData */
export const authedPostForm = (
  endpoint: string,
  formData: FormData,
  options: ApiOptions = {},
): Promise<Response> => {
  return apiFetch(endpoint, { method: 'POST', body: formData }, options);
};

/** Helper for authenticated PUT requests with a JSON body */
export const authedPut = <T>(endpoint: string, body: T, options: ApiOptions = {}): Promise<Response> => {
  return apiFetch(
    endpoint,
    { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) },
    options,
  );
};

/** Helper for authenticated DELETE requests */
export const authedDelete = (endpoint: string, options: ApiOptions = {}): Promise<Response> => {
  return apiFetch(endpoint, { method: 'DELETE' }, options);
};

/** Helper for authenticated DELETE requests with a JSON body */
export const authedDeleteWithBody = <T>(
  endpoint: string,
  body: T,
  options: ApiOptions = {},
): Promise<Response> => {
  return apiFetch(
    endpoint,
    {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    },
    options,
  );
};

/**
 * Pings the backend server to check if it's running and reachable.
 * @returns A promise that resolves to true if the server responds with 'pong'.
 */
export const pingBackend = (): Promise<Response> => publicGet('/health/ping');

/**
 * Checks the backend's database schema.
 * @returns A promise that resolves to an object with success status and a message.
 */
export const checkDbSchema = (): Promise<Response> => publicGet('/health/db-schema');

/**
 * Checks the backend's storage directory.
 * @returns A promise that resolves to an object with success status and a message.
 */
export const checkStorage = (): Promise<Response> => publicGet('/health/storage');

/**
 * Checks the backend's database connection pool health.
 * @returns A promise that resolves to an object with success status and a message.
 */
export const checkDbPoolHealth = (): Promise<Response> => publicGet('/health/db-pool');

/**
 * Checks the backend's Redis connection health.
 * @returns A promise that resolves to an object with success status and a message.
 */
export const checkRedisHealth = (): Promise<Response> => publicGet('/health/redis');

/**
 * Checks the status of the application process managed by PM2.
 * This is intended for development and diagnostic purposes.
 */
export const checkPm2Status = (): Promise<Response> => publicGet('/system/pm2-status');

/**
 * Fetches all flyers from the backend.
 * @returns A promise that resolves to an array of Flyer objects.
 */
export const fetchFlyers = (): Promise<Response> => publicGet('/flyers');

/**
 * Fetches a single flyer by its ID.
 * @param flyerId The ID of the flyer to fetch.
 * @returns A promise that resolves to the API response.
 */
export const fetchFlyerById = (flyerId: number): Promise<Response> =>
  publicGet(`/flyers/${flyerId}`);

/**
 * Fetches all master grocery items from the backend.
 * @returns A promise that resolves to an array of MasterGroceryItem objects.
 */
export const fetchMasterItems = (): Promise<Response> => {
  logger.debug('apiClient: fetchMasterItems called');
  return publicGet('/personalization/master-items');
};

/**
 * Fetches all categories from the backend.
 * @returns A promise that resolves to an array of Category objects.
 */
export const fetchCategories = (): Promise<Response> => publicGet('/categories');

// --- Flyer Processing API Function ---

/**
 * Uploads a flyer file to the backend to be processed asynchronously.
 * @param file The flyer file (PDF or image).
 * @param checksum The SHA-256 checksum of the file.
 * @param tokenOverride Optional token for testing.
 * @returns A promise that resolves to the API response, which should contain a `jobId`.
 */
export const uploadAndProcessFlyer = (
  file: File,
  checksum: string,
  tokenOverride?: string,
): Promise<Response> => {
  const formData = new FormData();
  formData.append('flyerFile', file);
  formData.append('checksum', checksum);
  return authedPostForm('/ai/upload-and-process', formData, { tokenOverride });
};
// --- Flyer Item API Functions ---

export const fetchFlyerItems = (flyerId: number): Promise<Response> =>
  publicGet(`/flyers/${flyerId}/items`);

export const fetchFlyerItemsForFlyers = async (flyerIds: number[]): Promise<Response> => {
  if (flyerIds.length === 0) {
    return new Response(JSON.stringify([]), { headers: { 'Content-Type': 'application/json' } });
  }
  return publicPost('/flyers/items/batch-fetch', { flyerIds });
};

export const countFlyerItemsForFlyers = async (flyerIds: number[]): Promise<Response> => {
  if (flyerIds.length === 0) {
    return new Response(JSON.stringify({ count: 0 }), {
      headers: { 'Content-Type': 'application/json' },
    });
  }
  return publicPost('/flyers/items/batch-count', { flyerIds });
};

// --- Store API Functions ---

/**
 * Uploads a new logo for a store.
 * @param storeId The ID of the store to update.
 * @param logoImage The logo image file.
 * @returns A promise that resolves with the new logo URL.
 */
export const uploadLogoAndUpdateStore = (
  storeId: number,
  logoImage: File,
  tokenOverride?: string,
): Promise<Response> => {
  const formData = new FormData();
  formData.append('logoImage', logoImage);
  return authedPostForm(`/stores/${storeId}/logo`, formData, { tokenOverride });
};

/**
 * Uploads a new logo for a brand. Requires admin privileges.
 * @param brandId The ID of the brand to update.
 * @param logoImage The logo image file.
 * @returns A promise that resolves with the new logo URL.
 */
export const uploadBrandLogo = (
  brandId: number,
  logoImage: File,
  tokenOverride?: string,
): Promise<Response> => {
  const formData = new FormData();
  formData.append('logoImage', logoImage);
  return authedPostForm(`/admin/brands/${brandId}/logo`, formData, { tokenOverride });
};

/**
 * Fetches historical price data for a given list of master item IDs.
 * @param masterItemIds An array of master grocery item IDs.
 * @returns A promise that resolves to an array of historical price records.
 */
export const fetchHistoricalPriceData = async (
  masterItemIds: number[],
  tokenOverride?: string,
): Promise<Response> => {
  if (masterItemIds.length === 0) {
    // Return a Response with an empty array
    return new Response(JSON.stringify([]), { headers: { 'Content-Type': 'application/json' } });
  }
  return authedPost('/price-history', { masterItemIds }, { tokenOverride });
};

// --- Watched Items API Functions ---

export const fetchWatchedItems = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/watched-items', { tokenOverride });

export const addWatchedItem = (
  itemName: string,
  category: string,
  tokenOverride?: string,
): Promise<Response> => authedPost('/users/watched-items', { itemName, category }, { tokenOverride });

export const removeWatchedItem = (
  masterItemId: number,
  tokenOverride?: string,
): Promise<Response> => authedDelete(`/users/watched-items/${masterItemId}`, { tokenOverride });

/**
 * Fetches the best current sale prices for all of the user's watched items.
 * @param tokenOverride Optional token for testing.
 * @returns A promise that resolves to an array of WatchedItemDeal objects.
 */
export const fetchBestSalePrices = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/deals/best-watched-prices', { tokenOverride });

// --- Shopping List API Functions ---

export const fetchShoppingLists = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/shopping-lists', { tokenOverride });

export const fetchShoppingListById = (
  listId: number,
  tokenOverride?: string,
): Promise<Response> => authedGet(`/users/shopping-lists/${listId}`, { tokenOverride });

export const createShoppingList = (name: string, tokenOverride?: string): Promise<Response> =>
  authedPost('/users/shopping-lists', { name }, { tokenOverride });

export const deleteShoppingList = (listId: number, tokenOverride?: string): Promise<Response> =>
  authedDelete(`/users/shopping-lists/${listId}`, { tokenOverride });

export const addShoppingListItem = (
  listId: number,
  item: { masterItemId?: number; customItemName?: string },
  tokenOverride?: string,
): Promise<Response> =>
  authedPost(`/users/shopping-lists/${listId}/items`, item, { tokenOverride });

export const updateShoppingListItem = (
  itemId: number,
  updates: Partial<ShoppingListItem>,
  tokenOverride?: string,
): Promise<Response> =>
  authedPut(`/users/shopping-lists/items/${itemId}`, updates, { tokenOverride });

export const removeShoppingListItem = (
  itemId: number,
  tokenOverride?: string,
): Promise<Response> => authedDelete(`/users/shopping-lists/items/${itemId}`, { tokenOverride });

/**
 * Fetches the full profile for the currently authenticated user.
 * It retrieves the auth token from local storage and sends it in the Authorization header.
 * @returns A promise that resolves to the user's combined UserProfile object.
 * @throws An error if the request fails or if the user is not authenticated.
 */
export const getAuthenticatedUserProfile = (options: ApiOptions = {}): Promise<Response> => {
  // The token is now passed to apiFetch, which handles the Authorization header.
  // If no token is provided (in browser context), apiFetch will get it from localStorage.
  return authedGet('/users/profile', options);
};

export async function loginUser(
  email: string,
  password: string,
  rememberMe: boolean,
): Promise<Response> {
  return publicPost('/auth/login', { email, password, rememberMe });
}

// --- Receipt Processing API Functions ---

/**
 * Uploads a receipt image to the backend for processing.
 * @param receiptImage The image file of the receipt.
 * @returns A promise that resolves with the backend's response, including the newly created receipt record.
 */
export const uploadReceipt = (
  receiptImage: File,
  tokenOverride?: string,
): Promise<Response> => {
  const formData = new FormData();
  formData.append('receiptImage', receiptImage);
  return authedPostForm('/receipts/upload', formData, { tokenOverride });
};

/**
 * Fetches the deals found for a specific processed receipt.
 * @param receiptId The ID of the processed receipt.
 * @returns A promise that resolves to an array of ReceiptDeal objects.
 */
export const getDealsForReceipt = (receiptId: number, tokenOverride?: string): Promise<Response> =>
  authedGet(`/receipts/${receiptId}/deals`, { tokenOverride });

// --- Analytics & Shopping Enhancement API Functions ---

export const trackFlyerItemInteraction = async (
  itemId: number,
  type: 'view' | 'click',
): Promise<void> => {
  // Add 'return' here so the promise chain is returned to the caller
  return apiFetch(`/flyer-items/${itemId}/track`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type }),
    keepalive: true, // Helps ensure the request is sent even if the page is closing
  })
    .then(() => {}) // Ensure return type is Promise<void>
    .catch((error) => logger.warn('Failed to track flyer item interaction', { error }));
};

export const logSearchQuery = async (
  query: Omit<SearchQuery, 'search_query_id' | 'id' | 'created_at' | 'user_id'>,
  tokenOverride?: string,
): Promise<void> => {
  // Add 'return' here
  return apiFetch(
    `/search/log`,
    {
      method: 'POST',
      body: JSON.stringify(query),
      keepalive: true,
    },
    { tokenOverride },
  )
    .then(() => {}) // Ensure return type is Promise<void>
    .catch((error) => logger.warn('Failed to log search query', { error }));
};

export const getPantryLocations = (tokenOverride?: string): Promise<Response> =>
  authedGet('/pantry/locations', { tokenOverride });

export const createPantryLocation = (name: string, tokenOverride?: string): Promise<Response> =>
  authedPost('/pantry/locations', { name }, { tokenOverride });

export const completeShoppingList = (
  shoppingListId: number,
  totalSpentCents?: number,
  tokenOverride?: string,
): Promise<Response> =>
  authedPost(
    `/users/shopping-lists/${shoppingListId}/complete`,
    { totalSpentCents },
    { tokenOverride },
  );

export const getShoppingTripHistory = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/shopping-history', { tokenOverride });

// --- Personalization & Social API Functions ---

export const getDietaryRestrictions = (): Promise<Response> =>
  publicGet('/personalization/dietary-restrictions');

export const getAppliances = (): Promise<Response> => publicGet('/personalization/appliances');

export const getUserDietaryRestrictions = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/me/dietary-restrictions', { tokenOverride });

export const setUserDietaryRestrictions = (
  restrictionIds: number[],
  tokenOverride?: string,
): Promise<Response> =>
  authedPut('/users/me/dietary-restrictions', { restrictionIds }, { tokenOverride });

export const getCompatibleRecipes = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/me/compatible-recipes', { tokenOverride });

export const getUserFeed = (
  limit: number = 20,
  offset: number = 0,
  tokenOverride?: string,
): Promise<Response> => authedGet(`/users/feed?limit=${limit}&offset=${offset}`, { tokenOverride });

export const forkRecipe = (
  originalRecipeId: number,
  tokenOverride?: string,
): Promise<Response> => authedPostEmpty(`/recipes/${originalRecipeId}/fork`, { tokenOverride });

export const followUser = (userIdToFollow: string, tokenOverride?: string): Promise<Response> =>
  authedPostEmpty(`/users/${userIdToFollow}/follow`, { tokenOverride });

export const unfollowUser = (
  userIdToUnfollow: string,
  tokenOverride?: string,
): Promise<Response> => authedDelete(`/users/${userIdToUnfollow}/follow`, { tokenOverride });

// --- Activity Log API Function ---

/**
 * Fetches a paginated list of recent activities from the backend.
 * @param limit The number of items to fetch.
 * @param offset The starting offset for pagination.
 * @returns A promise that resolves to an array of ActivityLogItem objects.
 */
export const fetchActivityLog = (
  limit: number = 20,
  offset: number = 0,
  tokenOverride?: string,
): Promise<Response> =>
  authedGet(`/admin/activity-log?limit=${limit}&offset=${offset}`, { tokenOverride });

// --- Favorite Recipes API Functions ---

/**
 * Retrieves a list of the currently authenticated user's favorite recipes.
 * @param tokenOverride Optional token for testing purposes.
 * @returns A promise that resolves to the API response.
 */
export const getUserFavoriteRecipes = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/me/favorite-recipes', { tokenOverride });

export const addFavoriteRecipe = (
  recipeId: number,
  tokenOverride?: string,
): Promise<Response> => authedPost('/users/me/favorite-recipes', { recipeId }, { tokenOverride });

export const removeFavoriteRecipe = (
  recipeId: number,
  tokenOverride?: string,
): Promise<Response> => authedDelete(`/users/me/favorite-recipes/${recipeId}`, { tokenOverride });

// --- Recipe Comments API Functions ---

export const getRecipeComments = (recipeId: number): Promise<Response> =>
  publicGet(`/recipes/${recipeId}/comments`);

/**
 * Fetches a single recipe by its ID.
 * @param recipeId The ID of the recipe to fetch.
 * @returns A promise that resolves to the API response.
 */
export const getRecipeById = (recipeId: number): Promise<Response> =>
  publicGet(`/recipes/${recipeId}`);

export const addRecipeComment = (
  recipeId: number,
  content: string,
  parentCommentId?: number,
  tokenOverride?: string,
): Promise<Response> =>
  authedPost(`/recipes/${recipeId}/comments`, { content, parentCommentId }, { tokenOverride });

/**
 * Deletes a recipe.
 * @param recipeId The ID of the recipe to delete.
 * @param tokenOverride Optional token for testing purposes.
 * @returns {Promise<Response>} A promise that resolves to the API response.
 */
export const deleteRecipe = (recipeId: number, tokenOverride?: string): Promise<Response> =>
  authedDelete(`/recipes/${recipeId}`, { tokenOverride });
// --- Admin API Functions for New Features ---

export const getUnmatchedFlyerItems = (tokenOverride?: string): Promise<Response> =>
  authedGet('/admin/unmatched-items', { tokenOverride });

export const updateRecipeStatus = (
  recipeId: number,
  status: 'private' | 'pending_review' | 'public' | 'rejected',
  tokenOverride?: string,
): Promise<Response> =>
  authedPut(`/admin/recipes/${recipeId}/status`, { status }, { tokenOverride });

export const updateRecipeCommentStatus = (
  commentId: number,
  status: 'visible' | 'hidden' | 'reported',
  tokenOverride?: string,
): Promise<Response> => authedPut(`/admin/comments/${commentId}/status`, { status }, { tokenOverride });

/**
 * Fetches all brands from the backend. Requires admin privileges.
 * @returns A promise that resolves to an array of Brand objects.
 */
export const fetchAllBrands = (tokenOverride?: string): Promise<Response> =>
  authedGet('/admin/brands', { tokenOverride });
export interface AppStats {
  // This interface should ideally be in types.ts
  flyerCount: number;
  userCount: number;
  flyerItemCount: number;
  storeCount: number;
  pendingCorrectionCount: number;
  recipeCount: number;
}

export interface DailyStat {
  date: string;
  new_users: number;
  new_flyers: number;
}

/**
 * Fetches daily user registration and flyer upload stats for the last 30 days.
 * @returns A promise that resolves to an array of daily stat objects.
 */
export const getDailyStats = (tokenOverride?: string): Promise<Response> =>
  authedGet('/admin/stats/daily', { tokenOverride });
/**
 * Fetches application-wide statistics. Requires admin privileges.
 * @returns A promise that resolves to an object containing app stats.
 */
export const getApplicationStats = (tokenOverride?: string): Promise<Response> =>
  authedGet('/admin/stats', { tokenOverride });

// --- Admin Correction API Functions ---

export const getSuggestedCorrections = (tokenOverride?: string): Promise<Response> =>
  authedGet('/admin/corrections', { tokenOverride });

export const getFlyersForReview = (tokenOverride?: string): Promise<Response> => {
  logger.debug('apiClient: calling getFlyersForReview');
  return authedGet('/admin/review/flyers', { tokenOverride });
};

export const approveCorrection = (
  correctionId: number,
  tokenOverride?: string,
): Promise<Response> => authedPostEmpty(`/admin/corrections/${correctionId}/approve`, { tokenOverride });

export const rejectCorrection = (
  correctionId: number,
  tokenOverride?: string,
): Promise<Response> => authedPostEmpty(`/admin/corrections/${correctionId}/reject`, { tokenOverride });

export const updateSuggestedCorrection = (
  correctionId: number,
  newSuggestedValue: string,
  tokenOverride?: string,
): Promise<Response> =>
  authedPut(
    `/admin/corrections/${correctionId}`,
    { suggested_value: newSuggestedValue },
    { tokenOverride },
  );

/**
 * Enqueues a job to clean up the files associated with a specific flyer.
 * Requires admin privileges.
 * @param flyerId The ID of the flyer to clean up.
 * @param tokenOverride Optional token for testing.
 */
export const cleanupFlyerFiles = (flyerId: number, tokenOverride?: string): Promise<Response> =>
  authedPostEmpty(`/admin/flyers/${flyerId}/cleanup`, { tokenOverride });

/**
 * Enqueues a test job designed to fail, for testing the Bull Board UI.
 * Requires admin privileges.
 * @param tokenOverride Optional token for testing.
 */
export const triggerFailingJob = (tokenOverride?: string): Promise<Response> =>
  authedPostEmpty('/admin/trigger/failing-job', { tokenOverride });

/**
 * Fetches the status of a background processing job.
 * @param jobId The ID of the job to check.
 * @param tokenOverride Optional token for testing.
 * @returns A promise that resolves to the API response with the job's status.
 */
export const getJobStatus = (jobId: string, tokenOverride?: string): Promise<Response> =>
  authedGet(`/ai/jobs/${jobId}/status`, { tokenOverride });

/**
 * Triggers the clearing of the geocoding cache on the server.
 * Requires admin privileges.
 * @param tokenOverride Optional token for testing.
 */
export const clearGeocodeCache = (tokenOverride?: string): Promise<Response> =>
  authedPostEmpty('/admin/system/clear-geocode-cache', { tokenOverride });

export function registerUser(
  email: string,
  password: string,
  fullName?: string,
  avatarUrl?: string,
): Promise<Response> {
  return publicPost('/auth/register', {
    email,
    password,
    full_name: fullName,
    avatar_url: avatarUrl,
  });
}

/**
 * Sends a password reset request for the given email to the backend.
 * @param email The user's email address.
 * @returns A promise that resolves with a success message.
 */
export function requestPasswordReset(email: string): Promise<Response> {
  return publicPost('/auth/forgot-password', { email });
}

/**
 * Sends the password reset token and new password to the backend.
 * @param token The password reset token from the URL.
 * @param newPassword The user's new password.
 * @returns A promise that resolves with a success message.
 */
export function resetPassword(token: string, newPassword: string): Promise<Response> {
  return publicPost('/auth/reset-password', { token, newPassword });
}
/**
 * Sends updated user preferences to the backend.
 * @param preferences A partial object of the user's preferences to update.
 * @returns A promise that resolves to the user's full, updated profile object.
 */
export async function updateUserPreferences(
  preferences: Partial<Profile['preferences']>,
  apiOptions: ApiOptions = {},
): Promise<Response> {
  return authedPut('/users/profile/preferences', preferences, apiOptions);
}

/**
 * Sends updated user profile data (name, avatar) to the backend.
 * @param profileData An object containing the full_name and/or avatar_url to update.
 * @returns A promise that resolves to the user's full, updated profile object.
 */
export function updateUserProfile(
  profileData: Partial<Profile>,
  apiOptions: ApiOptions = {},
): Promise<Response> {
  return authedPut('/users/profile', profileData, apiOptions);
}

/**
 * Fetches a complete export of the user's data from the backend.
 * @returns A promise that resolves to a JSON object of the user's data.
 */
export function exportUserData(apiOptions: ApiOptions = {}): Promise<Response> {
  return authedGet('/users/data-export', apiOptions);
}

export const getUserAppliances = (tokenOverride?: string): Promise<Response> =>
  authedGet('/users/appliances', { tokenOverride });

/**
 * Sets the kitchen appliances for the currently authenticated user.
 * This will replace all existing appliances with the new set.
 * @param applianceIds An array of numbers representing the IDs of the selected appliances.
 */
export const setUserAppliances = (
  applianceIds: number[],
  apiOptions: ApiOptions = {},
): Promise<Response> => authedPut('/users/appliances', { applianceIds }, apiOptions);

/**
 * Sends an address string to the backend to be geocoded.
 * @param address The full address string.
 * @param tokenOverride Optional token for testing.
 */
export const geocodeAddress = (address: string, apiOptions: ApiOptions = {}): Promise<Response> =>
  authedPost('/system/geocode', { address }, apiOptions);

/**
 * Fetches a specific address by its ID.
 * @param addressId The ID of the address to fetch.
 * @param tokenOverride Optional token for testing.
 */
export const getUserAddress = (addressId: number, apiOptions: ApiOptions = {}): Promise<Response> =>
  authedGet(`/users/addresses/${addressId}`, apiOptions);

/**
 * Creates or updates the authenticated user's primary address.
 * @param addressData The full address object.
 * @param tokenOverride Optional token for testing.
 */
export const updateUserAddress = (
  addressData: Partial<Address>,
  apiOptions: ApiOptions = {},
): Promise<Response> => authedPut('/users/profile/address', addressData, apiOptions);

/**
 * Sends a new password to the backend to be updated.
 * @param newPassword The user's new password.
 * @returns A promise that resolves on success.
 */
export async function updateUserPassword(
  newPassword: string,
  apiOptions: ApiOptions = {},
): Promise<Response> {
  return authedPut('/users/profile/password', { newPassword }, apiOptions);
}

/**
 * Updates the role of a specific user.
 * @param userId The ID of the user to update.
 * @param role The new role to assign ('user' or 'admin').
 * @returns A promise that resolves to the updated Profile object.
 */
export async function updateUserRole(
  userId: string,
  role: 'user' | 'admin',
  apiOptions: ApiOptions = {},
): Promise<Response> {
  return authedPut(`/admin/users/${userId}/role`, { role }, apiOptions);
}

/**
 * Sends a request to delete the user's account, verifying with their current password.
 * @param password The user's current password for verification.
 * @returns A promise that resolves on success.
 */
export async function deleteUserAccount(
  password: string,
  apiOptions: ApiOptions = {},
): Promise<Response> {
  return authedDeleteWithBody('/users/account', { password }, apiOptions);
}

// --- Notification API Functions ---

/**
 * Fetches notifications for the authenticated user.
 * @param limit The number of notifications to fetch.
 * @param offset The number of notifications to skip for pagination.
 * @returns A promise that resolves to the API response.
 */
export const getNotifications = (
  limit: number = 20,
  offset: number = 0,
  tokenOverride?: string,
): Promise<Response> =>
  authedGet(`/users/notifications?limit=${limit}&offset=${offset}`, { tokenOverride });

/**
 * Marks all of the user's unread notifications as read.
 * @returns A promise that resolves to the API response.
 */
export const markAllNotificationsAsRead = (tokenOverride?: string): Promise<Response> =>
  authedPostEmpty('/users/notifications/mark-all-read', { tokenOverride });

/**
 * Marks a single notification as read.
 * @param notificationId The ID of the notification to mark as read.
 * @returns A promise that resolves to the API response.
 */
export const markNotificationAsRead = (
  notificationId: number,
  tokenOverride?: string,
): Promise<Response> =>
  authedPostEmpty(`/users/notifications/${notificationId}/mark-read`, { tokenOverride });

// --- Budgeting and Spending Analysis API Functions ---

/**
 * Fetches all budgets for the authenticated user.
 * @returns A promise that resolves to the API response.
 */
export const getBudgets = (tokenOverride?: string): Promise<Response> =>
  authedGet('/budgets', { tokenOverride });

/**
 * Creates a new budget for the authenticated user.
 * @param budgetData The data for the new budget.
 * @returns A promise that resolves to the API response.
 */
export const createBudget = (
  budgetData: Omit<Budget, 'budget_id' | 'user_id' | 'created_at' | 'updated_at'>,
  tokenOverride?: string,
): Promise<Response> => authedPost('/budgets', budgetData, { tokenOverride });

/**
 * Updates an existing budget for the authenticated user.
 * @param budgetId The ID of the budget to update.
 * @param budgetData The data to update.
 * @returns A promise that resolves to the API response.
 */
export const updateBudget = (
  budgetId: number,
  budgetData: Partial<Omit<Budget, 'budget_id' | 'user_id' | 'created_at' | 'updated_at'>>,
  tokenOverride?: string,
): Promise<Response> => authedPut(`/budgets/${budgetId}`, budgetData, { tokenOverride });

/**
 * Deletes a budget for the authenticated user.
 * @param budgetId The ID of the budget to delete.
 * @returns A promise that resolves to the API response.
 */
export const deleteBudget = (budgetId: number, tokenOverride?: string): Promise<Response> =>
  authedDelete(`/budgets/${budgetId}`, { tokenOverride });

/**
 * Fetches the user's spending analysis for a given date range.
 * @param startDate The start date of the analysis period (YYYY-MM-DD).
 * @param endDate The end date of the analysis period (YYYY-MM-DD).
 * @returns A promise that resolves to the API response.
 */
export const getSpendingAnalysis = (
  startDate: string,
  endDate: string,
  tokenOverride?: string,
): Promise<Response> =>
  authedGet(`/budgets/spending-analysis?startDate=${startDate}&endDate=${endDate}`, { tokenOverride });

// --- Gamification API Functions ---

/**
 * Fetches the master list of all available achievements in the system.
 * This is a public endpoint.
 * @returns A promise that resolves to the API response.
 */
export const getAchievements = (): Promise<Response> => publicGet('/achievements');

/**
 * Fetches all achievements earned by the currently authenticated user.
 * @param tokenOverride Optional token for testing purposes.
 * @returns A promise that resolves to the API response.
 */
export const getUserAchievements = (tokenOverride?: string): Promise<Response> =>
  authedGet('/achievements/me', { tokenOverride });

/**
 * Fetches the public leaderboard of top users by points.
 * @param limit The number of users to fetch. Defaults to 10.
 * @returns A promise that resolves to the API response.
 */
export const fetchLeaderboard = (limit: number = 10): Promise<Response> =>
  publicGet(`/achievements/leaderboard?limit=${limit}`);

/**
 * Uploads a new avatar image for the authenticated user.
 * @param avatarFile The image file to upload.
 * @param tokenOverride Optional token for testing purposes.
 * @returns A promise that resolves to the API response containing the updated profile.
 */
export const uploadAvatar = (avatarFile: File, tokenOverride?: string): Promise<Response> => {
  const formData = new FormData();
  formData.append('avatar', avatarFile);
  return authedPostForm('/users/profile/avatar', formData, { tokenOverride });
};
