import React from 'react';
import {render} from '@testing-library/react-native';
import {localEventsHandler} from '@config/synchroniser';
import * as chatUtils from '@modules/chats/utils';
import type {Message} from '@modules/chats/interfaces';
import {ExtendedMessageType, MessageStatus} from '@modules/chats/interfaces';
import {UnreadsUpdaterService} from '@modules/chats/instances/updaters';
import {useReactionsInMessages} from '@modules/chats/store';
import {useUserId} from '@redux/selectors';

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

const mockGetLocalizedDateLabelByTs =
  chatUtils.getLocalizedDateLabelByTs as jest.Mock;
const mockHasUnsupportedBlocks = chatUtils.hasUnsupportedBlocks as jest.Mock;
const mockIsSystemMessage = chatUtils.isSystemMessage as jest.Mock;
const mockMapMessageWithLocalEvents =
  chatUtils.mapMessageWithLocalEvents as jest.Mock;
const mockUseReactionsInMessages = useReactionsInMessages as jest.Mock;
const mockUseUserId = useUserId as jest.Mock;
const mockGetEvents = localEventsHandler.getEvents as jest.Mock;
const mockUnreadsGet = UnreadsUpdaterService.get as jest.Mock;

jest.mock('@redux/selectors', () => ({
  useUserId: jest.fn(),
}));

jest.mock('@config/synchroniser', () => ({
  localEventsHandler: {
    getEvents: jest.fn(),
  },
}));

jest.mock('@modules/chats/utils', () => {
  const actualUtils = jest.requireActual('@modules/chats/utils');

  return {
    ...actualUtils,
    getLocalizedDateLabelByTs: jest.fn(),
    hasUnsupportedBlocks: jest.fn(),
    isSystemMessage: jest.fn(),
    mapMessageWithLocalEvents: jest.fn(),
  };
});

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

jest.mock('@modules/chats/instances/updaters', () => ({
  UnreadsUpdaterService: {
    get: jest.fn(),
  },
}));

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

  const HookWrapper = () => {
    result = useExtendedMessages(props);
    return null;
  };

  render(React.createElement(HookWrapper));

  return result!;
};

const createMessage = (overrides: Partial<Message> = {}): Message => ({
  text: 'hello',
  channel: 'channel-1',
  ts: '01',
  user: '1',
  client_msg_id: 'client-1',
  username: 'User',
  version: 1,
  height: 10,
  highlighted_spans: [],
  formatted_text: 'hello',
  ...overrides,
});

describe('useExtendedMessages', () => {
  beforeEach(() => {
    mockUseUserId.mockReset();
    mockGetEvents.mockReset();
    mockGetLocalizedDateLabelByTs.mockReset();
    mockHasUnsupportedBlocks.mockReset();
    mockIsSystemMessage.mockReset();
    mockMapMessageWithLocalEvents.mockReset();
    mockUnreadsGet.mockReset();
    mockUseReactionsInMessages.mockReset();

    mockUseUserId.mockReturnValue(1);
    mockGetEvents.mockReturnValue([]);
    mockGetLocalizedDateLabelByTs.mockReturnValue('Today');
    mockHasUnsupportedBlocks.mockReturnValue(false);
    mockIsSystemMessage.mockReturnValue(false);
    mockUnreadsGet.mockReturnValue(null);
    mockUseReactionsInMessages.mockReturnValue({});
  });

  it('adds a date pill and a default message with reactions', () => {
    const messages = [
      createMessage({
        reactions: [{name: '👍', count: 1, users: ['1']}],
      }),
    ];

    mockUseReactionsInMessages.mockReturnValue({'client-1': 1});

    const result = renderHook({
      messages,
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result.map(item => item.extendedType)).toEqual([
      ExtendedMessageType.DatePill,
      ExtendedMessageType.Default,
      ExtendedMessageType.Reactions,
    ]);
  });

  it('skips messages deleted from UI', () => {
    const result = renderHook({
      messages: [createMessage({deleted_from_ui: true})],
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result).toEqual([]);
  });

  it('marks hidden messages as deleted', () => {
    const result = renderHook({
      messages: [createMessage({hidden: true})],
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result[1].extendedType).toBe(ExtendedMessageType.DeletedMessage);
  });

  it('maps messages using local events', () => {
    const message = createMessage();
    mockGetEvents.mockReturnValue([{id: `event-${message.client_msg_id}`}]);
    mockMapMessageWithLocalEvents.mockReturnValue({
      ...message,
      text: 'updated',
    });

    const result = renderHook({
      messages: [message],
      channel: 'channel-1',
      is_channel: true,
    });

    expect(mockMapMessageWithLocalEvents).toHaveBeenCalled();
    expect((result[1].data as Message).text).toBe('updated');
  });

  it('adds unread pill after last read message', () => {
    mockUnreadsGet.mockReturnValue({
      count: 2,
      last_read: '02',
      latest_message_ts: '02',
    });

    const messages = [
      createMessage({ts: '02', status: MessageStatus.Sent}),
      createMessage({ts: '03', client_msg_id: 'client-2'}),
    ];

    const result = renderHook({
      messages,
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result.map(item => item.extendedType)).toContain(
      ExtendedMessageType.UnreadPill,
    );
  });

  it('marks unsupported blocks as unsupported', () => {
    mockHasUnsupportedBlocks.mockReturnValue(true);

    const result = renderHook({
      messages: [createMessage()],
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result[1].extendedType).toBe(ExtendedMessageType.UnsupportedMessage);
  });

  it('renders system messages when block is system', () => {
    mockIsSystemMessage.mockReturnValue(true);

    const result = renderHook({
      messages: [
        createMessage({
          blocks: [
            {type: 'channel_created'} as NonNullable<Message['blocks']>[0],
          ],
        }),
      ],
      channel: 'channel-1',
      is_channel: true,
    });

    expect(result[1].extendedType).toBe(ExtendedMessageType.SystemMessage);
    expect(result[1].data).toEqual(
      expect.objectContaining({
        height: 10,
      }),
    );
  });
});
