import React from 'react';

import { StyleSheet, Text } from 'react-native';
import { GestureDetector } from 'react-native-gesture-handler';

import { cleanup, render, screen, waitFor } from '@testing-library/react-native';
import type { Channel as ChannelType, StreamChat } from 'stream-chat';

import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext';
import type { ComponentOverrides } from '../../../../contexts/componentsContext/ComponentsContext';
import { WithComponents } from '../../../../contexts/componentsContext/ComponentsContext';
import { useMessageContext } from '../../../../contexts/messageContext/MessageContext';

import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
import { useMockedApis } from '../../../../mock-builders/api/useMockedApis';
import {
  generateAttachmentAction,
  generateGiphyAttachment,
} from '../../../../mock-builders/generator/attachment';
import { generateChannelResponse } from '../../../../mock-builders/generator/channel';
import { generateMember } from '../../../../mock-builders/generator/member';
import { generateMessage } from '../../../../mock-builders/generator/message';
import { generateUser } from '../../../../mock-builders/generator/user';
import { getTestClientWithUser } from '../../../../mock-builders/mock';
import { Channel } from '../../../Channel/Channel';
import { Chat } from '../../../Chat/Chat';
import { Message } from '../../Message';

describe('MessageItemView', () => {
  let channel: ChannelType;
  let chatClient: StreamChat;
  let renderMessage: (
    options: Omit<React.ComponentProps<typeof Message>, 'groupStyles'> &
      Partial<Pick<React.ComponentProps<typeof Message>, 'groupStyles'>>,
    channelProps?: Partial<React.ComponentProps<typeof Channel>>,
    componentOverrides?: ComponentOverrides,
  ) => ReturnType<typeof render>;

  const user = generateUser({ id: 'id', name: 'name' });
  const messages = [generateMessage({ user })];

  beforeEach(async () => {
    const members = [generateMember({ user })];
    const mockedChannel = generateChannelResponse({
      members,
      messages,
    });

    chatClient = await getTestClientWithUser(user);
    useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
    channel = chatClient.channel('messaging', mockedChannel.channel.id);

    renderMessage = (options, channelProps, componentOverrides) =>
      render(
        <ChannelsStateProvider>
          <Chat client={chatClient}>
            {componentOverrides ? (
              <WithComponents overrides={componentOverrides}>
                <Channel channel={channel} {...channelProps}>
                  <Message groupStyles={['bottom']} {...options} />
                </Channel>
              </WithComponents>
            ) : (
              <Channel channel={channel} {...channelProps}>
                <Message groupStyles={['bottom']} {...options} />
              </Channel>
            )}
          </Chat>
        </ChannelsStateProvider>,
      );
  });

  afterEach(() => {
    jest.clearAllMocks();
    cleanup();
  });

  it('renders the MessageItemView component', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.getByTestId('message-item-view-wrapper')).toBeTruthy();
    });
  });

  it('renders MessageAuthor when alignment is left', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.queryByTestId('message-author')).toBeDefined();
    });
  });

  it('do not renders MessageAuthor when alignment is right', async () => {
    const user = generateUser({ id: 'id', name: 'name' });
    const message = generateMessage({ user });
    renderMessage({ message });

    await waitFor(() => {
      expect(screen.queryByTestId('message-author')).toBeNull();
    });
  });

  it('renders MessageDeleted when message type is deleted', async () => {
    const user = generateUser();
    const message = generateMessage({ type: 'deleted', user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.queryByTestId('message-deleted')).toBeDefined();
    });
  });

  it('renders MessageContent when message is not deleted', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.queryByTestId('message-components')).toBeDefined();
    });
  });

  it('wraps the full MessageItemView with swipe-to-reply', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message });

    await waitFor(() => {
      const gestureDetectors = screen.UNSAFE_getAllByType(GestureDetector);
      const wrapsMessageItemView = gestureDetectors.some((detector) => {
        try {
          return Boolean(detector.findByProps({ testID: 'message-item-view-wrapper' }));
        } catch {
          return false;
        }
      });

      expect(wrapsMessageItemView).toBe(true);
    });
  });

  it('exposes contextMenuAnchorRef through MessageContext for custom renderers', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    const CustomMessageItemView = () => {
      const { contextMenuAnchorRef } = useMessageContext();

      return <Text ref={contextMenuAnchorRef}>Custom Message Item</Text>;
    };

    renderMessage({ message }, {}, { MessageItemView: CustomMessageItemView });

    await waitFor(() => {
      expect(screen.queryByText('Custom Message Item')).not.toBeNull();
    });
  });

  it('renders MessageSpacer component if defined', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message }, {}, { MessageSpacer: () => <Text>Message Spacer</Text> });

    await waitFor(() => {
      expect(screen.queryByText('Message Spacer')).not.toBeNull();
    });
  });

  it('renders ReactionListTop when reactionListPosition is top', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message }, { reactionListPosition: 'top' });

    await waitFor(() => {
      expect(screen.queryByLabelText('Reaction List Top')).toBeDefined();
      expect(screen.queryByLabelText('Reaction List Bottom')).toBeNull();
    });
  });

  it('renders ReactionListBottom when reactionListPosition is bottom', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message }, { reactionListPosition: 'bottom' });

    await waitFor(() => {
      expect(screen.queryByLabelText('Reaction List Top')).toBeNull();
      expect(screen.queryByLabelText('Reaction List Bottom')).toBeDefined();
    });
  });

  it('renders MessagePinnedHeader when message is pinned', async () => {
    const user = generateUser();
    const message = generateMessage({ pinned: true, user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.queryByLabelText('Message Pinned Header')).not.toBeNull();
    });
  });

  it('renders MessageHeader component if defined', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message }, {}, { MessageHeader: () => <Text>Message Header</Text> });

    await waitFor(() => {
      expect(screen.queryByText('Message Header')).not.toBeNull();
    });
  });

  it('keeps message-item-view-wrapper free of group-positional padding', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ groupStyles: ['top'], message });

    await waitFor(() => {
      const innerStyle = screen.getByTestId('message-item-view-wrapper').props.style;
      expect(innerStyle).toMatchObject({
        alignItems: 'flex-end',
        gap: 8,
        flexDirection: 'row',
      });
      expect(innerStyle.paddingTop).toBeUndefined();
      expect(innerStyle.paddingBottom).toBeUndefined();
    });
  });

  it('hoists the per-group padding delta onto message-wrapper for top messages', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ groupStyles: ['top'], message });

    await waitFor(() => {
      expect(screen.getByTestId('message-wrapper').props.style).toEqual(
        expect.arrayContaining([expect.objectContaining({ paddingTop: 8 })]),
      );
    });
  });

  it('hoists the per-group padding delta onto message-wrapper for bottom messages', async () => {
    const user = generateUser();
    const message = generateMessage({ user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.getByTestId('message-wrapper').props.style).toEqual(
        expect.arrayContaining([expect.objectContaining({ paddingBottom: 8 })]),
      );
    });
  });

  it('noBorder true when only emojis text', async () => {
    const user = generateUser();
    const message = generateMessage({ quoted_message: undefined, text: '😊', user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.getByTestId('message-content-wrapper').props.style[2]).toMatchObject({
        borderWidth: 0,
      });
    });
  });

  it('noBorder true when only other attachments is present', async () => {
    const user = generateUser();
    const message = generateMessage({ attachments: [{ title: 'other', type: 'other' }], user });

    renderMessage({ message });

    await waitFor(() => {
      expect(screen.getByTestId('message-content-wrapper').props.style[2]).toMatchObject({
        borderWidth: 0,
      });
    });
  });

  it('keeps the message shell background for a quoted reply with a giphy attachment', async () => {
    const user = generateUser();
    const message = generateMessage({
      attachments: [generateGiphyAttachment()],
      quoted_message: generateMessage({ text: 'quoted message', user }),
      text: '',
      user,
    });

    renderMessage({ message });

    await waitFor(() => {
      const wrapperStyle = StyleSheet.flatten(
        screen.getByTestId('message-content-wrapper').props.style,
      );
      expect(wrapperStyle.backgroundColor).not.toBe('transparent');
    });
  });

  it('renders a standalone shell for an ephemeral giphy preview with a quoted reply', async () => {
    const user = generateUser();
    const message = generateMessage({
      attachments: [
        {
          ...generateGiphyAttachment(),
          actions: [
            generateAttachmentAction(),
            generateAttachmentAction(),
            generateAttachmentAction(),
          ],
        },
      ],
      quoted_message: generateMessage({ text: 'quoted message', user }),
      text: '',
      user,
    });

    renderMessage({ message });

    await waitFor(() => {
      const wrapperStyle = StyleSheet.flatten(
        screen.getByTestId('message-content-wrapper').props.style,
      );
      expect(wrapperStyle.backgroundColor).toBe('transparent');
    });
  });
});
