// src/services/apiClient.test.ts
import { describe, it, expect, vi, afterAll, afterEach, beforeEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

// Unmock the module under test to ensure we are testing the real implementation.
vi.unmock('./apiClient');

import * as apiClient from './apiClient';

// Mock the logger to keep test output clean and verifiable.
vi.mock('./logger', () => ({
  logger: {
    debug: vi.fn(),
    info: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
  },
}));

// Mock localStorage for token storage, as it's used by apiFetch.
const localStorageMock = (() => {
  let store: Record<string, string> = {};
  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => {
      store[key] = value.toString();
    },
    removeItem: (key: string) => {
      delete store[key];
    },
    clear: () => {
      store = {};
    },
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

// Setup MSW mock server.
const server = setupServer();

describe('API Client', () => {
  // These variables will be used to capture details from the fetch call.
  let capturedUrl: URL | null = null;
  let capturedHeaders: Headers | null = null;
  let capturedBody: string | FormData | Record<string, unknown> | null = null;

  // Start the MSW server before all tests.
  beforeAll(() => server.listen());

  beforeEach(() => {
    // Define a correctly typed mock function for fetch.
    const mockFetch = (url: RequestInfo | URL, options?: RequestInit): Promise<Response> => {
      capturedUrl = new URL(url as string, 'http://localhost');
      capturedHeaders = options?.headers as Headers;

      if (typeof options?.body === 'string') {
        try {
          capturedBody = JSON.parse(options.body);
        } catch {
          capturedBody = options.body;
        }
      } else if (options?.body instanceof FormData) {
        capturedBody = options.body;
      } else {
        capturedBody = null;
      }

      return Promise.resolve(new Response(JSON.stringify({ data: 'mock-success' }), { status: 200, headers: new Headers() as Headers }));
    };

    // Use spyOn instead of direct assignment to allow restoration for MSW tests.
    vi.spyOn(global, 'fetch').mockImplementation(mockFetch);
  });

  afterEach(() => {
    server.resetHandlers();
    localStorageMock.clear();
    // Restore all mocks (including global.fetch) to their original implementation.
    vi.restoreAllMocks();
  });
  afterAll(() => server.close());

  describe('apiFetch', () => {
    it('should add Authorization header if token exists in localStorage', async () => {
      localStorage.setItem('authToken', 'test-token-123');

      // The global fetch mock will now capture the headers.
      await apiClient.apiFetch('/users/profile');

      expect(capturedHeaders).not.toBeNull();
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer test-token-123');
    });

    it('should not add Authorization header if no token exists', async () => {
      await apiClient.apiFetch('/public-data');

      expect(capturedHeaders).not.toBeNull();
      expect(capturedHeaders!.has('Authorization')).toBe(false);
    });

    it('should handle token refresh on 401 response', async () => {
      localStorage.setItem('authToken', 'expired-token'); // Set an initial token

      // Mock the fetch sequence on the existing spy:
      // 1. Initial API call fails with 401
      // 2. `refreshToken` call succeeds with a new token
      // 3. Retried API call succeeds with the expected user data
      vi.mocked(global.fetch)
        .mockResolvedValueOnce({ ok: false, status: 401, json: () => Promise.resolve({ message: 'Unauthorized' }) } as Response)
        .mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ token: 'new-refreshed-token' }) } as Response)
        .mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ user_id: 'user-123' }) } as Response);

      // The apiClient's internal refreshToken function will call the refresh endpoint.
      // We don't need a separate MSW handler for it if we are mocking global.fetch directly.
      // This test is now independent of MSW.
      const response = await apiClient.apiFetch('/users/profile');
      const data = await response.json();

      expect(response.ok).toBe(true);
      expect(data).toEqual({ user_id: 'user-123' });
      // Verify the new token was stored in localStorage.
      expect(localStorage.getItem('authToken')).toBe('new-refreshed-token');
    });

    it('should reject if token refresh fails', async () => {
      localStorage.setItem('authToken', 'expired-token');

      // Mock the fetch sequence:
      // 1. Initial API call fails with 401
      // 2. `refreshToken` call also fails (e.g., with a 403)
      vi.mocked(global.fetch)
        .mockResolvedValueOnce({ ok: false, status: 401, json: () => Promise.resolve({ message: 'Unauthorized' }) } as Response)
        .mockResolvedValueOnce({ ok: false, status: 403, json: () => Promise.resolve({ message: 'Refresh failed' }) } as Response);

      // The apiFetch call should ultimately reject.
      await expect(apiClient.apiFetch('/users/profile')).rejects.toThrow('Refresh failed');
    });

    it('should handle 401 on initial call, refresh token, and then poll until completed', async () => {
      localStorage.setItem('authToken', 'expired-token');
      // Mock the global fetch to return a sequence of responses:
      // 1. 401 Unauthorized (initial API call)
      // 2. 200 OK (token refresh call)
      // 3. 200 OK (retry of the initial API call)
      vi.mocked(global.fetch)
        .mockResolvedValueOnce({ ok: false, status: 401, json: () => Promise.resolve({ message: 'Unauthorized' }) } as Response)
        .mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ token: 'new-refreshed-token' }) } as Response)
        .mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ state: 'completed', returnValue: { flyerId: 777 } }) } as Response);

      const finalResponse = await apiClient.getJobStatus('polling-job');
      const finalData = await finalResponse.json();

      expect(finalData.state).toBe('completed');
      expect(localStorage.getItem('authToken')).toBe('new-refreshed-token');
    });
  });

  describe('apiFetch (with FormData)', () => {
    it('should handle FormData correctly by not setting Content-Type', async () => {
      localStorage.setItem('authToken', 'form-data-token');
      const formData = new FormData();
      formData.append('file', new File(['content'], 'test.jpg'));

      await apiClient.apiFetch('/ai/upload-and-process', {
        method: 'POST',
        body: formData,
      });

      expect(capturedHeaders).not.toBeNull();
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer form-data-token');
      // FIX: The browser sets the Content-Type for FormData, including a boundary.
      // We should assert that our code does NOT set it to 'application/json', and that the browser-set value is correct.
      // In our test mock, the header won't be set at all, which is the correct behavior for our code.
      // The browser/fetch implementation handles the multipart header.
      expect(capturedHeaders!.has('Content-Type')).toBe(false);
    });
  });

  describe('Store and Brand API Functions', () => {
    it('uploadLogoAndUpdateStore should send FormData with the logo', async () => {
      const mockFile = new File(['logo-content'], 'store-logo.png', { type: 'image/png' });
      await apiClient.uploadLogoAndUpdateStore(1, mockFile);

      expect(capturedUrl?.pathname).toBe('/api/stores/1/logo');
      expect(capturedBody).toBeInstanceOf(FormData);
      const uploadedFile = (capturedBody as FormData).get('logoImage') as File;
      expect(uploadedFile.name).toBe('store-logo.png');
    });

    it('uploadBrandLogo should send FormData with the logo to the admin endpoint', async () => {
      const mockFile = new File(['brand-logo-content'], 'brand-logo.svg', { type: 'image/svg+xml' });
      await apiClient.uploadBrandLogo(2, mockFile);

      expect(capturedUrl?.pathname).toBe('/api/admin/brands/2/logo');
      expect(capturedBody).toBeInstanceOf(FormData);
      const uploadedFile = (capturedBody as FormData).get('logoImage') as File;
      expect(uploadedFile.name).toBe('brand-logo.svg');
    });
  });

  describe('Specific API Functions', () => {
    beforeEach(() => {
        localStorage.setItem('authToken', 'specific-api-token');
    });

    it('getAuthenticatedUserProfile should call the correct endpoint', async () => {
      await apiClient.getAuthenticatedUserProfile();
      expect(capturedUrl?.pathname).toBe('/api/users/profile');
    });

    it('fetchWatchedItems should call the correct endpoint', async () => {
      await apiClient.fetchWatchedItems();
      expect(capturedUrl?.pathname).toBe('/api/users/watched-items');
    });

    it('addWatchedItem should send a POST request with the correct body', async () => {
      await apiClient.addWatchedItem('Apples', 'Produce');

      expect(capturedUrl?.pathname).toBe('/api/users/watched-items');
      expect(capturedBody).toEqual({ itemName: 'Apples', category: 'Produce' });
    });

    it('removeWatchedItem should send a DELETE request to the correct URL', async () => {
      await apiClient.removeWatchedItem(99);
      expect(capturedUrl?.pathname).toBe('/api/users/watched-items/99');
    });
  });

  describe('Budget API Functions', () => {
    it('getBudgets should call the correct endpoint', async () => {
      server.use(
        http.get('http://localhost/api/budgets', () => HttpResponse.json([]))
      );
      await apiClient.getBudgets();
      expect(capturedUrl?.pathname).toBe('/api/budgets');
    });

    it('createBudget should send a POST request with budget data', async () => {
      const budgetData = { name: 'Groceries', amount_cents: 50000, period: 'monthly' as const, start_date: '2024-01-01' };
      await apiClient.createBudget(budgetData);

      expect(capturedUrl?.pathname).toBe('/api/budgets');
      expect(capturedBody).toEqual(budgetData);
    });

    it('updateBudget should send a PUT request with the correct data and ID', async () => {
      const budgetUpdates = { amount_cents: 60000 };
      await apiClient.updateBudget(123, budgetUpdates);

      expect(capturedUrl?.pathname).toBe('/api/budgets/123');
      expect(capturedBody).toEqual(budgetUpdates);
    });

    it('deleteBudget should send a DELETE request to the correct URL', async () => {
      await apiClient.deleteBudget(456);
      expect(capturedUrl?.pathname).toBe('/api/budgets/456');
    });

    it('getSpendingAnalysis should send a GET request with correct query params', async () => {
      await apiClient.getSpendingAnalysis('2024-01-01', '2024-01-31');
      expect(capturedUrl!.searchParams.get('startDate')).toBe('2024-01-01');
      expect(capturedUrl!.searchParams.get('endDate')).toBe('2024-01-31');
    });
  });

  describe('Gamification API Functions', () => {
    it('getUserAchievements should call the authenticated endpoint', async () => {
      localStorage.setItem('authToken', 'gamify-token');
      await apiClient.getUserAchievements();

      expect(capturedUrl?.pathname).toBe('/api/achievements/me');
      expect(capturedHeaders).not.toBeNull();
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer gamify-token');
    });

    it('fetchLeaderboard should send a GET request with a limit query param', async () => {
      await apiClient.fetchLeaderboard(5);

      expect(capturedUrl).not.toBeNull(); // This assertion ensures capturedUrl is not null for the next line
      expect(capturedUrl!.pathname).toBe('/api/achievements/leaderboard');
      expect(capturedUrl!.searchParams.get('limit')).toBe('5');
    });

    it('getAchievements should call the public endpoint', async () => {
      // This is a public endpoint, so no token is needed.
      await apiClient.getAchievements();
      expect(capturedUrl?.pathname).toBe('/api/achievements');
    });

    it('uploadAvatar should send FormData with the avatar file', async () => {
      localStorage.setItem('authToken', 'avatar-token');
      const mockFile = new File(['avatar-content'], 'my-avatar.png', { type: 'image/png' });
      await apiClient.uploadAvatar(mockFile);

      expect(capturedHeaders!.get('Authorization')).toBe('Bearer avatar-token');
      expect(capturedBody).not.toBeNull();
      // Using non-null assertion (!) because we asserted not.toBeNull() above.
      const uploadedFile = (capturedBody as FormData).get('avatar') as File;
      expect(uploadedFile.name).toBe('my-avatar.png');
    });
  });

  describe('Notification API Functions', () => {
    it('getNotifications should call the correct endpoint with query params', async () => {
      await apiClient.getNotifications(10, 20);

      expect(capturedUrl).not.toBeNull();
      expect(capturedUrl!.pathname).toBe('/api/users/notifications');
      expect(capturedUrl!.searchParams.get('limit')).toBe('10');
      expect(capturedUrl!.searchParams.get('offset')).toBe('20');
    });

    it('markAllNotificationsAsRead should send a POST request', async () => {
      await apiClient.markAllNotificationsAsRead();
      expect(capturedUrl?.pathname).toBe('/api/users/notifications/mark-all-read');
    });

    it('markNotificationAsRead should send a POST request to the correct URL', async () => {
      const notificationId = 123;
      await apiClient.markNotificationAsRead(notificationId);
      expect(capturedUrl?.pathname).toBe(`/api/users/notifications/${notificationId}/mark-read`);
    });
  });

  describe('Shopping List API Functions', () => {
    // The beforeEach was testing fetchShoppingLists, so we move that into its own test.
    it('fetchShoppingLists should call the correct endpoint', async () => {
      await apiClient.fetchShoppingLists();
      expect(capturedUrl?.pathname).toBe('/api/users/shopping-lists');
    });

    it('fetchShoppingListById should call the correct endpoint', async () => {
      const listId = 5;
      await apiClient.fetchShoppingListById(listId);
      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/${listId}`);
    });

    it('createShoppingList should send a POST request with the list name', async () => {
      await apiClient.createShoppingList('Weekly Groceries');

      expect(capturedUrl?.pathname).toBe('/api/users/shopping-lists');
      expect(capturedBody).toEqual({ name: 'Weekly Groceries' });
    });

    it('deleteShoppingList should send a DELETE request to the correct URL', async () => {
      const listId = 42;
      server.use(
        http.delete(`http://localhost/api/users/shopping-lists/${listId}`, () => {
          return new HttpResponse(null, { status: 204 });
        })
      );
      await apiClient.deleteShoppingList(listId);
      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/${listId}`);
    });

    it('addShoppingListItem should send a POST request with item data', async () => {
      const listId = 42;
      const itemData = { customItemName: 'Paper Towels' };
      await apiClient.addShoppingListItem(listId, itemData);

      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/${listId}/items`);
      expect(capturedBody).toEqual(itemData);
    });

    it('updateShoppingListItem should send a PUT request with update data', async () => {
      const itemId = 101;
      const updates = { is_purchased: true };
      await apiClient.updateShoppingListItem(itemId, updates);

      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/items/${itemId}`);
      expect(capturedBody).toEqual(updates);
    });

    it('removeShoppingListItem should send a DELETE request to the correct URL', async () => {
      const itemId = 101;
      await apiClient.removeShoppingListItem(itemId);
      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/items/${itemId}`);
    });

    it('completeShoppingList should send a POST request with total spent', async () => {
      const listId = 77;
      const totalSpentCents = 12345;
      await apiClient.completeShoppingList(listId, totalSpentCents);

      expect(capturedUrl?.pathname).toBe(`/api/users/shopping-lists/${listId}/complete`);
      expect(capturedBody).toEqual({ totalSpentCents });
    });
  });

  describe('Recipe API Functions', () => {
    beforeEach(() => {
      // Most recipe endpoints require authentication.
      localStorage.setItem('authToken', 'recipe-token');
    });

    it('getCompatibleRecipes should call the correct endpoint', async () => {
      await apiClient.getCompatibleRecipes();
      expect(capturedUrl?.pathname).toBe('/api/users/me/compatible-recipes');
    });

    it('forkRecipe should send a POST request to the correct URL', async () => {
      const recipeId = 99;
      server.use(
        http.post(`http://localhost/api/recipes/${recipeId}/fork`, () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.forkRecipe(recipeId);
      expect(capturedUrl?.pathname).toBe(`/api/recipes/${recipeId}/fork`);
    });

    it('getUserFavoriteRecipes should call the correct endpoint', async () => {
      await apiClient.getUserFavoriteRecipes();
      expect(capturedUrl?.pathname).toBe('/api/users/me/favorite-recipes');
    });

    it('addFavoriteRecipe should send a POST request with the recipeId', async () => {
      const recipeId = 123;
      await apiClient.addFavoriteRecipe(recipeId);
      expect(capturedUrl?.pathname).toBe('/api/users/me/favorite-recipes');
      expect(capturedBody).toEqual({ recipeId });
    });

    it('removeFavoriteRecipe should send a DELETE request to the correct URL', async () => {
      const recipeId = 123;
      await apiClient.removeFavoriteRecipe(recipeId);
      expect(capturedUrl?.pathname).toBe(`/api/users/me/favorite-recipes/${recipeId}`);
    });

    it('getRecipeComments should call the public endpoint', async () => {
      const recipeId = 456;
      await apiClient.getRecipeComments(recipeId);
      expect(capturedUrl?.pathname).toBe(`/api/recipes/${recipeId}/comments`);
    });

    it('getRecipeById should call the correct public endpoint', async () => {
      const recipeId = 789;
      await apiClient.getRecipeById(recipeId);
      expect(capturedUrl?.pathname).toBe(`/api/recipes/${recipeId}`);
    });

    it('addRecipeComment should send a POST request with content and optional parentId', async () => {
      const recipeId = 456;
      const commentData = { content: 'This is a reply', parentCommentId: 789 };
      await apiClient.addRecipeComment(recipeId, commentData.content, commentData.parentCommentId);
      expect(capturedUrl?.pathname).toBe(`/api/recipes/${recipeId}/comments`);
      expect(capturedBody).toEqual(commentData);
    });

    it('deleteRecipe should send a DELETE request to the correct URL', async () => {
      const recipeId = 101;
      await apiClient.deleteRecipe(recipeId);
      expect(capturedUrl?.pathname).toBe(`/api/recipes/${recipeId}`);
    });
  });

  describe('User Profile and Settings API Functions', () => {
    it('updateUserProfile should send a PUT request with profile data', async () => {
      localStorage.setItem('authToken', 'user-settings-token');
      const profileData = { full_name: 'John Doe' };
      await apiClient.updateUserProfile(profileData, { tokenOverride: 'override-token' });
      expect(capturedUrl?.pathname).toBe('/api/users/profile');
      expect(capturedBody).toEqual(profileData);
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer override-token');
    });

    it('updateUserPreferences should send a PUT request with preferences data', async () => {
      const preferences = { darkMode: true };
      await apiClient.updateUserPreferences(preferences);
    expect(capturedUrl?.pathname).toBe('/api/users/profile/preferences');
      expect(capturedBody).toEqual(preferences);
    });

    it('updateUserPassword should send a PUT request with the new password', async () => {
      const passwordData = { newPassword: 'new-secure-password' };
      await apiClient.updateUserPassword(passwordData.newPassword, { tokenOverride: 'pw-override-token' });
      expect(capturedUrl?.pathname).toBe('/api/users/profile/password');
      expect(capturedBody).toEqual(passwordData);
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer pw-override-token');
    });

    it('updateUserPassword should send a PUT request with the new password', async () => {
      const newPassword = 'new-secure-password';
      await apiClient.updateUserPassword(newPassword);
      expect(capturedUrl?.pathname).toBe('/api/users/profile/password');
      expect(capturedBody).toEqual({ newPassword });
    });

    it('exportUserData should call the correct endpoint', async () => {
      await apiClient.exportUserData();
      expect(capturedUrl?.pathname).toBe('/api/users/data-export');
    });

    it('getUserFeed should call the correct endpoint with query params', async () => {
      await apiClient.getUserFeed(10, 5);
      expect(capturedUrl?.pathname).toBe('/api/users/feed');
      expect(capturedUrl!.searchParams.get('limit')).toBe('10');
      expect(capturedUrl!.searchParams.get('offset')).toBe('5');
    });

    it('followUser should send a POST request to the correct URL', async () => {
      const userIdToFollow = 'user-to-follow';
      await apiClient.followUser(userIdToFollow);
      expect(capturedUrl?.pathname).toBe(`/api/users/${userIdToFollow}/follow`);
    });

    it('unfollowUser should send a DELETE request to the correct URL', async () => {
      const userIdToUnfollow = 'user-to-unfollow';
      await apiClient.unfollowUser(userIdToUnfollow);
      expect(capturedUrl?.pathname).toBe(`/api/users/${userIdToUnfollow}/follow`);
    });

    it('registerUser should send a POST request with user data', async () => {
      await apiClient.registerUser('test@example.com', 'password123', 'Test User');
      expect(capturedUrl?.pathname).toBe('/api/auth/register');
      expect(capturedBody).toEqual({ email: 'test@example.com', password: 'password123', full_name: 'Test User', avatar_url: undefined });
    });

    it('deleteUserAccount should send a DELETE request with the confirmation password', async () => {
      const passwordData = { password: 'current-password-for-confirmation' };
      await apiClient.deleteUserAccount(passwordData.password);
      expect(capturedUrl?.pathname).toBe('/api/users/account');
      expect(capturedBody).toEqual(passwordData);
    });

    it('setUserDietaryRestrictions should send a PUT request with restriction IDs', async () => {
      const restrictionData = { restrictionIds: [1, 5] };
      await apiClient.setUserDietaryRestrictions(restrictionData.restrictionIds);
      expect(capturedUrl?.pathname).toBe('/api/users/me/dietary-restrictions');
      expect(capturedBody).toEqual(restrictionData);
    });

    it('setUserAppliances should send a PUT request with appliance IDs', async () => {
      const applianceData = { applianceIds: [2, 8] };
      await apiClient.setUserAppliances(applianceData.applianceIds, { tokenOverride: 'appliance-override' });
      expect(capturedUrl?.pathname).toBe('/api/users/appliances');
      expect(capturedBody).toEqual(applianceData);
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer appliance-override');
    });

    it('updateUserAddress should send a PUT request with address data', async () => {
      const addressData = { address_line_1: '123 Main St', city: 'Anytown' };
      await apiClient.updateUserAddress(addressData);
      expect(capturedUrl?.pathname).toBe('/api/users/profile/address');
      expect(capturedBody).toEqual(addressData);
    });

    it('geocodeAddress should send a POST request with address data', async () => {
      const address = '1600 Amphitheatre Parkway, Mountain View, CA';
      await apiClient.geocodeAddress(address);
      expect(capturedUrl?.pathname).toBe('/api/system/geocode');
      expect(capturedBody).toEqual({ address });
    });

    it('getUserAddress should call the correct endpoint', async () => {
      const addressId = 99;
      await apiClient.getUserAddress(addressId);
      expect(capturedUrl?.pathname).toBe(`/api/users/addresses/${addressId}`);
    });

    it('getPantryLocations should call the correct endpoint', async () => {
      await apiClient.getPantryLocations();
      expect(capturedUrl?.pathname).toBe('/api/pantry/locations');
    });

    it('createPantryLocation should send a POST request with the location name', async () => {
      await apiClient.createPantryLocation('Fridge');
      expect(capturedUrl?.pathname).toBe('/api/pantry/locations');
      expect(capturedBody).toEqual({ name: 'Fridge' });
    });

    it('getShoppingTripHistory should call the correct endpoint', async () => {
      localStorage.setItem('authToken', 'user-settings-token');
      await apiClient.getShoppingTripHistory();
      expect(capturedUrl?.pathname).toBe('/api/users/shopping-history');
      expect(capturedHeaders!.get('Authorization')).toBe('Bearer user-settings-token');
    });

    it('requestPasswordReset should send a POST request with email', async () => {
      const email = 'forgot@example.com';
      await apiClient.requestPasswordReset(email);
      expect(capturedUrl?.pathname).toBe('/api/auth/forgot-password');
      expect(capturedBody).toEqual({ email });
    });

    it('resetPassword should send a POST request with token and new password', async () => {
      const data = { token: 'reset-token', newPassword: 'new-password' };
      await apiClient.resetPassword(data.token, data.newPassword);
      expect(capturedUrl?.pathname).toBe('/api/auth/reset-password');
      expect(capturedBody).toEqual(data);
    });
  });

  describe('Public API Functions', () => {
    it('pingBackend should call the correct health check endpoint', async () => {
      await apiClient.pingBackend();
      expect(capturedUrl?.pathname).toBe('/api/health/ping');
    });

    it('checkDbSchema should call the correct health check endpoint', async () => {
      server.use(
        http.get('http://localhost/api/health/db-schema', () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.checkDbSchema();
      expect(capturedUrl?.pathname).toBe('/api/health/db-schema');
    });

    it('checkStorage should call the correct health check endpoint', async () => {
      server.use(
        http.get('http://localhost/api/health/storage', () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.checkStorage();
      expect(capturedUrl?.pathname).toBe('/api/health/storage');
    });

    it('checkDbPoolHealth should call the correct health check endpoint', async () => {
      server.use(
        http.get('http://localhost/api/health/db-pool', () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.checkDbPoolHealth();
      expect(capturedUrl?.pathname).toBe('/api/health/db-pool');
    });

    it('checkRedisHealth should call the correct health check endpoint', async () => {
      server.use(
        http.get('http://localhost/api/health/redis', () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.checkRedisHealth();
      expect(capturedUrl?.pathname).toBe('/api/health/redis');
    });

    it('checkPm2Status should call the correct system endpoint', async () => {
      server.use(
        http.get('http://localhost/api/system/pm2-status', () => {
          return HttpResponse.json({ success: true });
        })
      );
      await apiClient.checkPm2Status();
      expect(capturedUrl?.pathname).toBe('/api/system/pm2-status');
    });

    it('fetchFlyers should call the correct public endpoint', async () => {
      server.use(
        http.get('http://localhost/api/flyers', () => {
          return HttpResponse.json([]);
        })
      );
      await apiClient.fetchFlyers();
      expect(capturedUrl?.pathname).toBe('/api/flyers');
    });

    it('fetchMasterItems should call the correct public endpoint', async () => {
      server.use(
        http.get('http://localhost/api/personalization/master-items', () => {
          return HttpResponse.json([]);
        })
      );
      await apiClient.fetchMasterItems();
      expect(capturedUrl?.pathname).toBe('/api/personalization/master-items');
    });

    it('fetchCategories should call the correct public endpoint', async () => {
      server.use(
        http.get('http://localhost/api/categories', () => {
          return HttpResponse.json([]);
        })
      );
      await apiClient.fetchCategories();
      expect(capturedUrl?.pathname).toBe('/api/categories');
    });

    it('fetchFlyerItems should call the correct public endpoint for a specific flyer', async () => {
      const flyerId = 123;
      server.use(
        http.get(`http://localhost/api/flyers/${flyerId}/items`, () => {
          return HttpResponse.json([]);
        })
      );
      await apiClient.fetchFlyerItems(flyerId);
      expect(capturedUrl?.pathname).toBe(`/api/flyers/${flyerId}/items`);
    });

    it('fetchFlyerById should call the correct public endpoint for a specific flyer', async () => {
      const flyerId = 456;
      await apiClient.fetchFlyerById(flyerId);
      expect(capturedUrl?.pathname).toBe(`/api/flyers/${flyerId}`);
    });

    it('fetchFlyerItemsForFlyers should send a POST request with flyer IDs', async () => {
      const flyerIds = [1, 2, 3];
      await apiClient.fetchFlyerItemsForFlyers(flyerIds);
      expect(capturedUrl?.pathname).toBe('/api/flyers/items/batch-fetch');
      expect(capturedBody).toEqual({ flyerIds });
    });

    it('countFlyerItemsForFlyers should send a POST request with flyer IDs', async () => {
      const flyerIds = [1, 2, 3];
      await apiClient.countFlyerItemsForFlyers(flyerIds);
      expect(capturedUrl?.pathname).toBe('/api/flyers/items/batch-count');
      expect(capturedBody).toEqual({ flyerIds });
    });

    it('fetchHistoricalPriceData should send a POST request with master item IDs', async () => {
      const masterItemIds = [10, 20];
      await apiClient.fetchHistoricalPriceData(masterItemIds);
      expect(capturedUrl?.pathname).toBe('/api/price-history');
      expect(capturedBody).toEqual({ masterItemIds });
    });
  });

  describe('Admin API Functions', () => {
    it('approveCorrection should send a POST request to the correct URL', async () => {
      const correctionId = 45;
      await apiClient.approveCorrection(correctionId);
      expect(capturedUrl?.pathname).toBe(`/api/admin/corrections/${correctionId}/approve`);
    });

    it('updateRecipeStatus should send a PUT request with the correct body', async () => {
      const recipeId = 78;
      const statusUpdate = { status: 'public' as const };
      await apiClient.updateRecipeStatus(recipeId, 'public');
      expect(capturedUrl?.pathname).toBe(`/api/admin/recipes/${recipeId}/status`);
      expect(capturedBody).toEqual(statusUpdate);
    });

    it('cleanupFlyerFiles should send a POST request to the correct URL', async () => {
      const flyerId = 99;
      await apiClient.cleanupFlyerFiles(flyerId);
      expect(capturedUrl?.pathname).toBe(`/api/admin/flyers/${flyerId}/cleanup`);
    });

    it('triggerFailingJob should send a POST request to the correct URL', async () => {
      await apiClient.triggerFailingJob();
      expect(capturedUrl?.pathname).toBe('/api/admin/trigger/failing-job');
    });

    it('clearGeocodeCache should send a POST request to the correct URL', async () => {
      await apiClient.clearGeocodeCache();
      expect(capturedUrl?.pathname).toBe('/api/admin/system/clear-geocode-cache');
    });

    it('getApplicationStats should call the correct endpoint', async () => {
      await apiClient.getApplicationStats();
      expect(capturedUrl?.pathname).toBe('/api/admin/stats');
    });

    it('getSuggestedCorrections should call the correct endpoint', async () => {
      await apiClient.getSuggestedCorrections();
      expect(capturedUrl?.pathname).toBe('/api/admin/corrections');
    });

    it('rejectCorrection should send a POST request to the correct URL', async () => {
      const correctionId = 46;
      await apiClient.rejectCorrection(correctionId);
      expect(capturedUrl?.pathname).toBe(`/api/admin/corrections/${correctionId}/reject`);
    });

    it('updateSuggestedCorrection should send a PUT request with the new value', async () => {
      const correctionId = 47;
      const newValue = 'new value';
      await apiClient.updateSuggestedCorrection(correctionId, newValue);
      expect(capturedUrl?.pathname).toBe(`/api/admin/corrections/${correctionId}`);
      expect(capturedBody).toEqual({ suggested_value: newValue });
    });

    it('getUnmatchedFlyerItems should call the correct endpoint', async () => {
      await apiClient.getUnmatchedFlyerItems();
      expect(capturedUrl?.pathname).toBe('/api/admin/unmatched-items');
    });

    it('updateRecipeCommentStatus should send a PUT request with the status', async () => {
      const commentId = 88;
      await apiClient.updateRecipeCommentStatus(commentId, 'hidden');
      expect(capturedUrl?.pathname).toBe(`/api/admin/comments/${commentId}/status`);
      expect(capturedBody).toEqual({ status: 'hidden' });
    });

    it('fetchAllBrands should call the correct endpoint', async () => {
      await apiClient.fetchAllBrands();
      expect(capturedUrl?.pathname).toBe('/api/admin/brands');
    });

    it('getDailyStats should call the correct endpoint', async () => {
      await apiClient.getDailyStats();
      expect(capturedUrl?.pathname).toBe('/api/admin/stats/daily');
    });

    it('updateUserRole should send a PUT request with the new role', async () => {
      const userId = 'user-to-promote';
      await apiClient.updateUserRole(userId, 'admin');
      expect(capturedUrl?.pathname).toBe(`/api/admin/users/${userId}/role`);
      expect(capturedBody).toEqual({ role: 'admin' });
    });
  });

  describe('Analytics API Functions', () => {
    it('trackFlyerItemInteraction should send a POST request with interaction type', async () => {
      await apiClient.trackFlyerItemInteraction(123, 'click');
      expect(capturedUrl?.pathname).toBe('/api/flyer-items/123/track');
      expect(capturedBody).toEqual({ type: 'click' });
    });

    it('logSearchQuery should send a POST request with query data', async () => {
      const queryData = { query_text: 'apples', result_count: 10, was_successful: true };
      await apiClient.logSearchQuery(queryData);
      expect(capturedUrl?.pathname).toBe('/api/search/log');
      expect(capturedBody).toEqual(queryData);
    });
  });

  describe('Authentication API Functions', () => {
    it('loginUser should send a POST request with credentials', async () => {
      await apiClient.loginUser('test@example.com', 'password123', true);
      expect(capturedUrl?.pathname).toBe('/api/auth/login');
      expect(capturedBody).toEqual({ email: 'test@example.com', password: 'password123', rememberMe: true });
    });
  });

  describe('Admin Activity Log', () => {
    it('fetchActivityLog should call the correct endpoint with query params', async () => {
      await apiClient.fetchActivityLog(50, 10);
      expect(capturedUrl?.pathname).toBe('/api/admin/activity-log');
      expect(capturedUrl!.searchParams.get('limit')).toBe('50');
      expect(capturedUrl!.searchParams.get('offset')).toBe('10');
    });
  });

  describe('AI API Functions', () => {
    it('uploadAndProcessFlyer should send FormData with file and checksum', async () => {
      const mockFile = new File(['flyer-content'], 'flyer.pdf', { type: 'application/pdf' });
      const checksum = 'checksum-abc-123';
      await apiClient.uploadAndProcessFlyer(mockFile, checksum);

      expect(capturedUrl?.pathname).toBe('/api/ai/upload-and-process');
      expect(capturedBody).toBeInstanceOf(FormData);
      const uploadedFile = (capturedBody as FormData).get('flyerFile') as File;
      const sentChecksum = (capturedBody as FormData).get('checksum');
      expect(uploadedFile.name).toBe('flyer.pdf');
      expect(sentChecksum).toBe(checksum);
    });

    it('getJobStatus should call the correct endpoint', async () => {
      const jobId = 'job-xyz-789';
      await apiClient.getJobStatus(jobId);
      expect(capturedUrl?.pathname).toBe(`/api/ai/jobs/${jobId}/status`);
    });
  });

  describe('Receipt API Functions', () => {
    it('uploadReceipt should send FormData with the receipt image', async () => {
      const mockFile = new File(['receipt-content'], 'receipt.jpg', { type: 'image/jpeg' });
      await apiClient.uploadReceipt(mockFile);

      expect(capturedUrl?.pathname).toBe('/api/receipts/upload');
      expect(capturedBody).toBeInstanceOf(FormData);
      const uploadedFile = (capturedBody as FormData).get('receiptImage') as File;
      expect(uploadedFile.name).toBe('receipt.jpg');
    });

    it('getDealsForReceipt should call the correct endpoint', async () => {
      const receiptId = 55;
      await apiClient.getDealsForReceipt(receiptId);
      expect(capturedUrl?.pathname).toBe(`/api/receipts/${receiptId}/deals`);
    });
  });

  describe('Public Personalization API Functions', () => {
    it('getDietaryRestrictions should call the correct endpoint', async () => {
      await apiClient.getDietaryRestrictions();
      expect(capturedUrl?.pathname).toBe('/api/personalization/dietary-restrictions');
    });

    it('getAppliances should call the correct endpoint', async () => {
      await apiClient.getAppliances();
      expect(capturedUrl?.pathname).toBe('/api/personalization/appliances');
    });

    it('getUserDietaryRestrictions should call the correct endpoint', async () => {
      await apiClient.getUserDietaryRestrictions();
      expect(capturedUrl?.pathname).toBe('/api/users/me/dietary-restrictions');
    });

    it('getUserAppliances should call the correct endpoint', async () => {
      await apiClient.getUserAppliances();
      expect(capturedUrl?.pathname).toBe('/api/users/appliances');
    });
  });
});