/*
  Unit tests for src/helpers/rest.js
  - Uses Jest-style tests with module mocking for apiFetch
  - Verifies URLs, HTTP methods, bodies, and returned values
*/

// Mock the apiFetch module used by rest.js
jest.mock('../../src/helpers/apiFetch', () => ({
  __esModule: true,
  default: jest.fn()
}));

import apiFetch from '../../src/helpers/apiFetch';
import * as rest from '../../src/helpers/rest';

beforeEach(() => {
  jest.clearAllMocks();
});

const mockResolved = (value) => {
  apiFetch.mockResolvedValue(value);
};

const expectCalledWith = (url, options) => {
  expect(apiFetch).toHaveBeenCalledTimes(1);
  if (options !== undefined) {
    expect(apiFetch).toHaveBeenCalledWith(url, options);
  } else {
    expect(apiFetch).toHaveBeenCalledWith(url);
  }
};

describe('rest helpers', () => {
  test('getPersonById calls correct URL', async () => {
    const payload = { id: 42, name: 'John' };
    mockResolved(payload);

    const result = await rest.getPersonById(42);

    expectCalledWith('/users/42/profile');
    expect(result).toEqual(payload);
  });

  test('getUserDetail builds URLSearchParams from options', async () => {
    const payload = { ok: true };
    mockResolved(payload);
    const options = { id: 7, role: 'admin', active: '1' };

    const result = await rest.getUserDetail(options);

    const query = new URLSearchParams({ role: 'admin', active: '1' }).toString();
    expectCalledWith(`/users/7?${query}`);
    expect(result).toEqual(payload);
  });

  test('getPeople sends POST with body and pagination', async () => {
    const payload = [{ id: 1 }];
    mockResolved(payload);
    const data = { name: 'Jane' };

    const result = await rest.getPeople({ data, offset: 10, limit: 5 });

    expectCalledWith('/filter?offset=10&limit=5', {
      method: 'POST',
      body: JSON.stringify(data)
    });
    expect(result).toEqual(payload);
  });

  test('getPeopleByIds posts ids under user_ids', async () => {
    mockResolved({ ok: 1 });

    const result = await rest.getPeopleByIds({ userId: 99, ids: [1, 2, 3] });

    expectCalledWith('/users/99/filter_by_ids', {
      method: 'POST',
      body: JSON.stringify({ user_ids: [1, 2, 3] })
    });
    expect(result).toEqual({ ok: 1 });
  });

  test('simple GET endpoints', async () => {
    mockResolved(['genre1']);
    expect(await rest.getGenres()).toEqual(['genre1']);
    expectCalledWith('/genres');

    jest.clearAllMocks();
    mockResolved(['instr1']);
    expect(await rest.getInstruments()).toEqual(['instr1']);
    expectCalledWith('/instruments');

    jest.clearAllMocks();
    mockResolved({ id: 5 });
    expect(await rest.getCurrentUser()).toEqual({ id: 5 });
    expectCalledWith('/me');
  });

  test('updateUser POSTs body', async () => {
    const data = { name: 'Jane' };
    mockResolved({ ok: true });

    const res = await rest.updateUser(5, data);

    expectCalledWith('/users/5', { method: 'POST', body: JSON.stringify(data) });
    expect(res).toEqual({ ok: true });
  });

  test('friends endpoints', async () => {
    mockResolved([{ id: 1 }]);
    expect(await rest.getFriends(7)).toEqual([{ id: 1 }]);
    expectCalledWith('/users/7/friends');

    jest.clearAllMocks();
    mockResolved({ ok: true });
    expect(await rest.addFriend(7, 10)).toEqual({ ok: true });
    expectCalledWith('/users/7/friend_requests', {
      method: 'POST',
      body: JSON.stringify({ friend_id: 10 })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    expect(await rest.removeFriend(7, 10)).toEqual({ ok: true });
    expectCalledWith('/users/7/friends/10', { method: 'DELETE' });
  });

  test('getTextMessages uses URLSearchParams', async () => {
    mockResolved([{ id: 1 }]);
    const options = { thread_id: 'abc', page: '2' };

    const result = await rest.getTextMessages(options);

    const query = new URLSearchParams(options).toString();
    expectCalledWith(`/text_messages?${query}`);
    expect(result).toEqual([{ id: 1 }]);
  });

  test('createTextMessage posts JSON body', async () => {
    mockResolved({ id: 11, text: 'hi' });
    const options = { text: 'hi' };

    const result = await rest.createTextMessage(options);

    expectCalledWith('/text_messages', { method: 'POST', body: JSON.stringify(options) });
    expect(result).toEqual({ id: 11, text: 'hi' });
  });

  test('notifications endpoints', async () => {
    mockResolved([{ id: 1 }]);
    const res = await rest.getNotifications(9, { page: '1' });
    expectCalledWith(`/users/9/my_notifications?${new URLSearchParams({ page: '1' }).toString()}`);
    expect(res).toEqual([{ id: 1 }]);

    jest.clearAllMocks();
    mockResolved({ ok: true });
    expect(await rest.readNotifications(9)).toEqual({ ok: true });
    expectCalledWith('/users/9/my_notifications/read', { method: 'POST' });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    expect(await rest.deleteNotification(9, 2)).toEqual({ ok: true });
    expectCalledWith('/users/9/notifications/2', { method: 'DELETE' });
  });

  test('sessions endpoints (GET variants)', async () => {
    mockResolved([{}]);
    expect(await rest.getSessions()).toEqual([{}]);
    expectCalledWith('/sessions');

    jest.clearAllMocks();
    mockResolved([{}]);
    expect(await rest.getFriendsSessions()).toEqual([{}]);
    expectCalledWith('/sessions/friends');

    jest.clearAllMocks();
    mockResolved([{}]);
    expect(await rest.getPublicSessions()).toEqual([{}]);
    expectCalledWith('/sessions/public');

    jest.clearAllMocks();
    mockResolved([{}]);
    expect(await rest.getInactiveSessions()).toEqual([{}]);
    expectCalledWith('/sessions/inactive');
  });

  test('getSessionsHistory uses URLSearchParams', async () => {
    mockResolved([{ id: 1 }]);
    const options = { page: '3', per_page: '20' };

    const result = await rest.getSessionsHistory(options);

    const query = new URLSearchParams(options).toString();
    expectCalledWith(`/sessions/history?${query}`);
    expect(result).toEqual([{ id: 1 }]);
  });

  test('createSession posts options (note: function does not resolve)', () => {
    // Do not await this promise as the implementation does not resolve/reject
    rest.createSession({ foo: 'bar' });
    expectCalledWith('/sessions', { method: 'post', body: JSON.stringify({ options: { foo: 'bar' } }) });
  });

  test('getLatencyToUsers builds repeated user_ids', async () => {
    mockResolved({ ok: true });

    const res = await rest.getLatencyToUsers(10, [1, 2, 3]);

    expectCalledWith('/users/10/latencies?user_ids[]=1&user_ids[]=2&user_ids[]=3');
    expect(res).toEqual({ ok: true });
  });

  test('location endpoints (countries/regions/cities)', async () => {
    mockResolved(['US']);
    expect(await rest.getCountries()).toEqual(['US']);
    expectCalledWith('/countries');

    jest.clearAllMocks();
    mockResolved(['CA-BC']);
    expect(await rest.getRegions('CA')).toEqual(['CA-BC']);
    expectCalledWith('/regions?country=CA');

    jest.clearAllMocks();
    mockResolved(['Vancouver']);
    expect(await rest.getCities('CA', 'BC')).toEqual(['Vancouver']);
    expectCalledWith('/cities?country=CA&region=BC');
  });

  test('account email/password update endpoints', async () => {
    mockResolved({ ok: true });
    await rest.postUpdateAccountEmail(1, { email: 'a@example.com', current_password: 'pw' });
    expectCalledWith('/users/1/update_email_alt', {
      method: 'POST',
      body: JSON.stringify({ update_email: 'a@example.com', current_password: 'pw' })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.postUpdateAccountPassword(1, { new_password: 'npw', current_password: 'opw' });
    expectCalledWith('/users/1/set_password', {
      method: 'POST',
      body: JSON.stringify({ old_password: 'opw', new_password: 'npw', new_password_confirm: 'npw' })
    });
  });

  test('password reset endpoints', async () => {
    mockResolved({ ok: true });
    await rest.requestPasswordReset(1);
    expectCalledWith('/users/1/request_reset_password', { method: 'POST' });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.resetPassword('a@example.com');
    expectCalledWith('/users/reset_password', {
      method: 'POST',
      body: JSON.stringify({ email: 'a@example.com' })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.requstResetForgotPassword('a@example.com');
    expectCalledWith('/request_reset_forgot_password', {
      method: 'POST',
      body: JSON.stringify({ email: 'a@example.com' })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.resetForgotPassword({ token: 't1', password: 'pw' });
    expectCalledWith('/reset_forgot_password', {
      method: 'POST',
      body: JSON.stringify({ token: 't1', password: 'pw' })
    });
  });

  test('analytics and subscription endpoints', async () => {
    mockResolved({ ok: true });
    await rest.postUserAppInteraction(5, { action: 'click' });
    expectCalledWith('/users/5/app_interactions', { method: 'POST', body: JSON.stringify({ action: 'click' }) });

    jest.clearAllMocks();
    mockResolved({ plan: 'pro' });
    expect(await rest.getSubscription()).toEqual({ plan: 'pro' });
    expectCalledWith('/recurly/get_subscription');

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.changeSubscription('pro');
    expectCalledWith('/recurly/change_subscription', { method: 'POST', body: JSON.stringify({ plan_code: 'pro' }) });

    jest.clearAllMocks();
    mockResolved([{ id: 1 }]);
    const opts = { page: '1', per_page: '10' };
    expect(await rest.getInvoiceHistory(opts)).toEqual([{ id: 1 }]);
    expectCalledWith(`/recurly/invoice_history?${new URLSearchParams(opts).toString()}`);
  });

  test('affiliate endpoints', async () => {
    mockResolved({ id: 1 });
    await rest.createAffiliatePartner({ name: 'Acme' });
    expectCalledWith('/affiliate_partners', { method: 'POST', body: JSON.stringify({ name: 'Acme' }) });

    jest.clearAllMocks();
    mockResolved({ id: 2 });
    expect(await rest.getAffiliatePartnerData(5)).toEqual({ id: 2 });
    expectCalledWith('/users/5/affiliate_partner');

    jest.clearAllMocks();
    mockResolved({ items: [] });
    const expectedQuery = new URLSearchParams({ per_page: 10, page: 0 }).toString();
    expect(await rest.getAffiliateSignups({})).toEqual({ items: [] });
    expectCalledWith(`/affiliate_partners/signups?${expectedQuery}`);

    jest.clearAllMocks();
    mockResolved([]);
    expect(await rest.getAffiliatePayments()).toEqual([]);
    expectCalledWith('/affiliate_partners/payments');

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.postAffiliatePartnerData(5, { a: 1 });
    expectCalledWith('/users/5/affiliate_partner', { method: 'POST', body: JSON.stringify({ a: 1 }) });
  });

  test('jamtrack endpoints', async () => {
    mockResolved([{ name: 'x' }]);
    await rest.autocompleteJamTracks('g', 5);
    expectCalledWith(`/jamtracks/autocomplete?${new URLSearchParams({ match: 'g', limit: 5 }).toString()}`);

    jest.clearAllMocks();
    mockResolved([{ id: 1 }]);
    await rest.getPurchasedJamTracks({ page: '1' });
    const purchasedQuery = new URLSearchParams({ page: '1', show_purchased_only: true }).toString();
    expectCalledWith(`/jamtracks/purchased?${purchasedQuery}`);

    jest.clearAllMocks();
    mockResolved([{ id: 2 }]);
    await rest.getJamTrackArtists({ page: '2' });
    expectCalledWith(`/jamtracks/artists?${new URLSearchParams({ page: '2' }).toString()}`);

    jest.clearAllMocks();
    mockResolved([{ id: 3 }]);
    await rest.getJamTracks({ page: '3' });
    expectCalledWith(`/jamtracks?${new URLSearchParams({ page: '3' }).toString()}`);

    jest.clearAllMocks();
    mockResolved({ id: 7 });
    await rest.getJamTrack({ id: 7, include: 'all' });
    expectCalledWith(`/jamtracks/7?${new URLSearchParams({ include: 'all' }).toString()}`);

    jest.clearAllMocks();
    mockResolved({ id: 'abc' });
    await rest.getJamTrackPublic({ plan_code: 'abc' });
    expectCalledWith(`/jamtracks/band/abc?${new URLSearchParams({ plan_code: 'abc' }).toString()}`);

    jest.clearAllMocks();
    mockResolved({ slug: 's1' });
    process.env.REACT_APP_CLIENT_BASE_URL = 'http://client';
    await rest.getJamTrackBySlug({ slug: 's1' });
    expectCalledWith('/jamtracks/s1', { baseUrl: 'http://client' });
  });

  test('shopping cart endpoints', async () => {
    mockResolved({ ok: true });
    await rest.addJamtrackToShoppingCart({ id: 1 });
    expectCalledWith('/shopping_carts/add_jamtrack', { method: 'POST', body: JSON.stringify({ id: 1 }) });

    jest.clearAllMocks();
    mockResolved({ items: [] });
    await rest.getShoppingCart();
    expectCalledWith('/shopping_carts');

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.removeShoppingCart({ id: 1 });
    expectCalledWith('/shopping_carts', { method: 'DELETE', body: JSON.stringify({ id: 1 }) });
  });

  test('billing and recurly endpoints', async () => {
    mockResolved({ info: {} });
    await rest.getBillingInfo();
    expectCalledWith('/recurly/billing_info');

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.updateBillingInfo({ card: '4111' });
    expectCalledWith('/recurly/update_billing_info', {
      method: 'PUT',
      body: JSON.stringify({ billing_info: { card: '4111' } })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.createRecurlyAccount({ email: 'a@example.com' });
    expectCalledWith('/recurly/create_account', {
      method: 'POST',
      body: JSON.stringify({ email: 'a@example.com' })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.placeOrder();
    expectCalledWith('/recurly/place_order', { method: 'POST' });
  });

  test('user event endpoints', async () => {
    mockResolved({ ok: true });
    await rest.postUserEvent({ name: 'x' });
    expectCalledWith('/users/event/record', { method: 'POST', body: JSON.stringify({ name: 'x' }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.userOpenedJamTrackWebPlayer();
    expectCalledWith('/users/progression/opened_jamtrack_web_player', { method: 'POST' });
  });

  test('mixdown endpoints', async () => {
    mockResolved({ ok: true });
    await rest.markMixdownActive({ id: 1, selected_tracks: [1, 2] });
    expectCalledWith('/jamtracks/1/mixdowns/active', {
      method: 'POST',
      body: JSON.stringify({ selected_tracks: [1, 2] })
    });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.createMixdown({ jamtrack_id: 1 });
    expectCalledWith('/mixdowns', { method: 'POST', body: JSON.stringify({ jamtrack_id: 1 }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.enqueueMixdown({ id: 2, priority: 'high' });
    expectCalledWith('/mixdowns/2/enqueue', { method: 'POST', body: JSON.stringify({ priority: 'high' }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.deleteMixdown(3);
    expectCalledWith('/mixdowns/3', { method: 'DELETE' });
  });

  test('avatar endpoints', async () => {
    mockResolved({ ok: true });
    await rest.updateAvatar({ id: 1, url: 'http://image', crop_selection: { x: 1 } });
    expectCalledWith('/users/1/avatar_v2', { method: 'POST', body: JSON.stringify({ url: 'http://image' }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.deleteAvatar(1);
    expectCalledWith('/users/1/avatar_v2', { method: 'DELETE' });
  });

  test('alerts and downloads', async () => {
    mockResolved({ id: 1 });
    await rest.createAlert('subject', { a: 1 });
    expectCalledWith('/alerts', { method: 'POST', body: JSON.stringify({ subject: 'subject', data: { a: 1 } }) });

    jest.clearAllMocks();
    mockResolved({ clients: [] });
    await rest.getClientDownloads();
    expectCalledWith('/artifacts/clients');

    jest.clearAllMocks();
    mockResolved({ obs: [] });
    await rest.getObsPluginDownloads();
    expectCalledWith('/artifacts/OBSPlugin');
  });

  test('payments and gateways', async () => {
    mockResolved({ ok: true });
    await rest.paypalPlaceOrder({ token: 'abc' });
    expectCalledWith('/paypal/checkout/confirm', { method: 'POST', body: JSON.stringify({ token: 'abc' }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.submitStripe({ paymentMethodId: 'pm_1' });
    expectCalledWith('/stripe', { method: 'POST', body: JSON.stringify({ paymentMethodId: 'pm_1' }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.updatePayment({ a: 1 });
    expectCalledWith('/recurly/update_payment', { method: 'POST', body: JSON.stringify({ a: 1 }) });

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.updateEmail(5, 'a@example.com', 'pw');
    expectCalledWith('/users/5/update_email', { method: 'POST', body: JSON.stringify({ update_email: 'a@example.com', current_password: 'pw' }) });
  });

  test('lobby endpoints', async () => {
    mockResolved([{ id: 1 }]);
    await rest.getLobbyUsers();
    expectCalledWith('/users/lobby');

    jest.clearAllMocks();
    mockResolved([{ id: 2 }]);
    await rest.getLobbyChatMessages({ page: '1' });
    expectCalledWith(`/chat?${new URLSearchParams({ page: '1' }).toString()}`);

    jest.clearAllMocks();
    mockResolved({ ok: true });
    await rest.createLobbyChatMessage({ text: 'hi' });
    expectCalledWith('/chat', { method: 'POST', body: JSON.stringify({ text: 'hi' }) });
  });

  test('app features', async () => {
    mockResolved({ features: [] });
    await rest.getAppFeatures('prod');
    expectCalledWith('/app_features?env=prod');
  });
});
