import type {TaggedUser} from '@interfaces/taggedUsers';
import {ALL_MENTION, DEFAULT_MENTION_NAME} from '@modules/chats/constants';
import {ChatUserStatus, HighlightedSpanType} from '@modules/chats/interfaces';
import {NBSP} from '@shared/constants';

const mockGetUserInfo = jest.fn();
const mockUsersMapperGet = jest.fn();
const mockUsersMapperSave = jest.fn();
const mockSentryError = jest.fn();

jest.mock('@modules/chats/services', () => ({
  getUserInfo: (...args: unknown[]) => mockGetUserInfo(...args),
}));

jest.mock('@modules/chats/instances/UsersMapper', () => ({
  usersMapper: {
    get: (...args: unknown[]) => mockUsersMapperGet(...args),
    save: (...args: unknown[]) => mockUsersMapperSave(...args),
  },
}));

jest.mock('@shared/utils', () => ({
  sentryLogs: {
    error: (...args: unknown[]) => mockSentryError(...args),
  },
}));

let getFullNameForMention: typeof import('../mentions').getFullNameForMention;
let getHighlightedSpans: typeof import('../mentions').getHighlightedSpans;
let getTextWithHighlights: typeof import('../mentions').getTextWithHighlights;
let prepareMessageWithMentions: typeof import('../mentions').prepareMessageWithMentions;

describe('mentions utils', () => {
  beforeAll(() => {
    jest.isolateModules(() => {
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const mentions = require('../mentions') as typeof import('../mentions');
      ({
        getFullNameForMention,
        getHighlightedSpans,
        getTextWithHighlights,
        prepareMessageWithMentions,
      } = mentions);
    });
  });

  beforeEach(() => {
    mockGetUserInfo.mockReset();
    mockUsersMapperGet.mockReset();
    mockUsersMapperSave.mockReset();
    mockSentryError.mockReset();
  });

  it('extracts highlighted spans for mentions and all-mentions', () => {
    const text = `Hello <@U12> and ${ALL_MENTION.command}!`;
    const mentionIndex = text.indexOf('<@U12>');
    const allIndex = text.indexOf(ALL_MENTION.command);

    const spans = getHighlightedSpans(text);

    expect(spans).toEqual([
      {
        type: HighlightedSpanType.Mention,
        user_id: 12,
        start: mentionIndex,
        end: mentionIndex + '<@U12>'.length,
      },
      {
        type: HighlightedSpanType.AllMention,
        start: allIndex,
        end: allIndex + ALL_MENTION.command.length,
      },
    ]);
  });

  it('replaces tagged users with mention commands', () => {
    const message = 'Hi John and all';
    const taggedUsers: TaggedUser[] = [
      {
        start: 3,
        length: 4,
        type: {user: {id: 10}},
      },
      {
        start: 12,
        length: 3,
        type: {user: {id: ALL_MENTION.id}},
      },
    ];

    const updated = prepareMessageWithMentions(message, taggedUsers);

    expect(updated).toBe('Hi <@U10> and <!channel>');
  });

  it('builds full name for mentions', () => {
    expect(
      getFullNameForMention({
        span: {type: HighlightedSpanType.AllMention, start: 0, end: 0},
      }),
    ).toBe(ALL_MENTION.label);

    expect(
      getFullNameForMention({
        span: {type: HighlightedSpanType.Mention, start: 0, end: 0, user_id: 1},
      }),
    ).toBe(DEFAULT_MENTION_NAME);

    expect(
      getFullNameForMention({
        span: {type: HighlightedSpanType.Mention, start: 0, end: 0, user_id: 1},
        user: {first_name: 'John', last_name: 'Doe'},
      }),
    ).toBe(`John${NBSP}Doe`);
  });

  it('replaces mentions using local user info', async () => {
    mockUsersMapperGet.mockImplementation((userId: number) =>
      userId === 10
        ? {
            first_name: 'John',
            last_name: 'Doe',
          }
        : undefined,
    );

    const message = `Hi <@U10> and ${ALL_MENTION.command}.`;
    const spans = getHighlightedSpans(message);

    const updated = await getTextWithHighlights({
      message,
      highlightedSpans: spans,
    });

    expect(updated).toBe(`Hi @John${NBSP}Doe and @all.`);
    expect(mockGetUserInfo).not.toHaveBeenCalled();
    expect(mockUsersMapperSave).not.toHaveBeenCalled();
  });

  it('fetches user info when mention is not cached', async () => {
    mockUsersMapperGet.mockImplementation(() => undefined);
    mockGetUserInfo.mockResolvedValueOnce([
      {
        id: 10,
        first_name: 'Jane',
        last_name: 'Smith',
        picture_asset_url: 'https://cdn.test/avatar.png',
        status: ChatUserStatus.Active,
        deleted_at: null,
      },
    ]);

    const message = 'Hello <@U10>';
    const spans = getHighlightedSpans(message);

    const updated = await getTextWithHighlights({
      message,
      highlightedSpans: spans,
    });

    expect(updated).toBe(`Hello @Jane${NBSP}Smith`);
    expect(mockGetUserInfo).toHaveBeenCalledWith({user: 10});
    expect(mockUsersMapperSave).toHaveBeenCalledWith([
      {
        id: 10,
        first_name: 'Jane',
        last_name: 'Smith',
        profile_picture: 'https://cdn.test/avatar.png',
        status: ChatUserStatus.Active,
        deleted_at: null,
      },
    ]);
  });
});
