import React from 'react';
import {render, act} from '@testing-library/react-native';
import {getChannelUsersForMentions} from '@modules/chats/db';

import {useChannelUsersForMentions} from '../useChannelUsersForMentions';

const mockGetChannelUsersForMentions = getChannelUsersForMentions as jest.Mock;

jest.mock('lodash.debounce', () => {
  return (fn: (...args: unknown[]) => unknown) => {
    const debounced = (...args: unknown[]) => fn(...args);
    debounced.cancel = jest.fn();
    debounced.flush = jest.fn();
    return debounced;
  };
});

jest.mock('@services/localStorage', () => ({
  persistedUserId: 1,
}));

jest.mock('@modules/chats/db', () => ({
  getChannelUsersForMentions: jest.fn(),
}));

const renderHook = (
  props: Parameters<typeof useChannelUsersForMentions>[0],
) => {
  let result: ReturnType<typeof useChannelUsersForMentions> | undefined;

  const HookWrapper = (hookProps: typeof props) => {
    result = useChannelUsersForMentions(hookProps);
    return null;
  };

  const utils = render(React.createElement(HookWrapper, props));

  return {
    getResult: () => result!,
    rerender: (nextProps: typeof props) =>
      utils.rerender(React.createElement(HookWrapper, nextProps)),
  };
};

describe('useChannelUsersForMentions', () => {
  const channel = 'channel-1';
  const onSearchResultIsEmpty = jest.fn();

  beforeEach(() => {
    mockGetChannelUsersForMentions.mockReset();
    onSearchResultIsEmpty.mockReset();
  });

  it('fetches members on search and excludes current user', async () => {
    mockGetChannelUsersForMentions.mockResolvedValueOnce([]);

    renderHook({channel, search: 'john', onSearchResultIsEmpty});

    await act(async () => {
      await mockGetChannelUsersForMentions.mock.results[0]?.value;
    });

    expect(mockGetChannelUsersForMentions).toHaveBeenCalledWith({
      channel,
      page: 1,
      search: 'john',
      excludeUserIds: [1],
      limit: 20,
    });
    expect(onSearchResultIsEmpty).toHaveBeenCalledWith('john');
  });

  it('loads more members when has next page', async () => {
    mockGetChannelUsersForMentions
      .mockResolvedValueOnce(
        Array.from({length: 20}, (_, index) => ({
          id: index + 2,
          firstName: `User${index + 2}`,
          lastName: 'Test',
          profilePicture: null,
        })),
      )
      .mockResolvedValueOnce([
        {id: 100, firstName: 'User100', lastName: 'Test', profilePicture: null},
      ]);

    const {getResult} = renderHook({
      channel,
      search: '',
      onSearchResultIsEmpty,
    });

    await act(async () => {
      await mockGetChannelUsersForMentions.mock.results[0]?.value;
    });

    expect(getResult().hasNextPage).toBe(true);

    await act(async () => {
      await getResult().getMoreMembers();
    });

    expect(mockGetChannelUsersForMentions).toHaveBeenCalledWith({
      channel,
      page: 2,
      search: '',
      excludeUserIds: [1],
      limit: 20,
    });
    expect(getResult().groupMembers.length).toBe(21);
  });

  it('does not fetch more when there is no next page', async () => {
    mockGetChannelUsersForMentions.mockResolvedValueOnce([
      {id: 2, firstName: 'User2', lastName: 'Test', profilePicture: null},
    ]);

    const {getResult} = renderHook({
      channel,
      search: '',
      onSearchResultIsEmpty,
    });

    await act(async () => {
      await mockGetChannelUsersForMentions.mock.results[0]?.value;
    });

    await act(async () => {
      await getResult().getMoreMembers();
    });

    expect(mockGetChannelUsersForMentions).toHaveBeenCalledTimes(1);
  });
});
