/* eslint-disable @typescript-eslint/no-var-requires */
import type {
  Asset,
  Block,
  ExtendedMessage,
  Message,
  MediaEntity,
} from '@modules/chats/interfaces';
import {
  ExtendedMessageType,
  MediaType,
  MessageStatus,
} from '@modules/chats/interfaces';
import {
  MAX_REACTIONS_FOR_MESSAGE,
  MAX_REACTIONS_PER_LINE,
} from '@modules/chats/constants';

let calculateGifWidthAndHeight: typeof import('../measuring').calculateGifWidthAndHeight;
let calculateTextHeight: typeof import('../measuring').calculateTextHeight;
let CHATS_SPACING: typeof import('../measuring').CHATS_SPACING;
let getAttachmentsHeight: typeof import('../measuring').getAttachmentsHeight;
let getMessageMargins: typeof import('../measuring').getMessageMargins;
let getReactionText: typeof import('../reactions').getReactionText;
let getReactionsHeight: typeof import('../measuring').getReactionsHeight;
let getReactionsLines: typeof import('../measuring').getReactionsLines;
let getEstimatedMessageSize: typeof import('../measuring').getEstimatedMessageSize;
let MESSAGE_TEXT_MAX_WIDTH: typeof import('../measuring').MESSAGE_TEXT_MAX_WIDTH;
let MESSAGE_TEXT_STYLES: typeof import('../measuring').MESSAGE_TEXT_STYLES;

let mockGetLineCount: jest.Mock;

const mockIsSystemMessage = jest.fn();
const mockIsMessageReplied = jest.fn();
const mockFormatNumber = jest.fn();
const mockGetMessageReactionsCount = jest.fn();

jest.mock('react-native', () => ({
  Platform: {
    OS: 'ios',
    select: jest.fn((options: Record<string, unknown>) => options.ios),
  },
  Dimensions: {
    get: jest.fn(() => ({width: 360, height: 640})),
  },
  PixelRatio: {
    get: jest.fn(() => 2),
    getFontScale: jest.fn(() => 1),
    roundToNearestPixel: jest.fn(value => value),
  },
  StyleSheet: {
    create: (styles: Record<string, unknown>) => styles,
  },
}));

jest.mock('react-native-text-size', () => ({
  getLineCount: jest.fn(),
}));

jest.mock('react-native-svg', () => ({}));

jest.mock('@config/i18n', () => ({
  language: 'es',
}));

jest.mock('@shared/constants', () => ({
  FONT_SCALE: 1,
  windowDimensions: {width: 360},
}));

jest.mock('@shared/theme', () => ({
  SPACING: {
    x2: 8,
    x1: 4,
    'x0.5': 2,
  },
}));

jest.mock('@shared/utils', () => ({
  formatNumber: (...args: unknown[]) => mockFormatNumber(...args),
  isAndroid: false,
}));

jest.mock('@modules/chats/store/zustand/reactions', () => ({
  getMessageReactionsCount: (...args: unknown[]) =>
    mockGetMessageReactionsCount(...args),
}));

jest.mock('../blocks', () => ({
  isMessageReplied: (...args: unknown[]) => mockIsMessageReplied(...args),
  isSystemMessage: (...args: unknown[]) => mockIsSystemMessage(...args),
}));

jest.mock('../computation', () => ({
  computeElegantNumber: (value: number) => value,
}));

describe('measuring utils', () => {
  const baseAsset = {
    id: 1,
    mimetype: 'image/png',
    filename: 'image.png',
  };

  const createAudioAsset = (): Asset => ({
    ...baseAsset,
    metadata: {format: 'audio'},
    url: 'https://example.com/audio.mp3',
    size: 100,
    isValid: true,
    audio_duration_ms: 1000,
  });

  const createImageAsset = (): Asset => ({
    ...baseAsset,
    metadata: {format: 'image'},
    image_url: 'https://example.com/image.png',
    image_bytes: 1000,
    image_height: 120,
    image_width: 160,
  });

  const createPendingAsset = (): MediaEntity => ({
    id: 'media-1',
    type: MediaType.IMAGE,
    fileAsset: null,
    status: 'idle',
    contentType: 'image/png',
    localUri: 'file://pending.png',
    name: 'pending.png',
    sizeInBytes: 100,
  });

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

  const createDefaultItem = (
    overrides: Partial<ExtendedMessage> = {},
  ): ExtendedMessage =>
    ({
      id: 'extended-1',
      extendedType: ExtendedMessageType.Default,
      data: createDefaultMessage(),
      isChannel: true,
      showSenderInfo: false,
      isSentByCurrentUser: false,
      ...overrides,
    } as ExtendedMessage);

  beforeEach(() => {
    jest.resetModules();
    mockGetLineCount = jest.requireMock('react-native-text-size')
      .getLineCount as jest.Mock;
    mockGetLineCount.mockReset();
    mockIsSystemMessage.mockReset();
    mockIsMessageReplied.mockReset();
    mockFormatNumber.mockReset();
    mockGetMessageReactionsCount.mockReset();

    jest.isolateModules(() => {
      const measuring =
        require('../measuring') as typeof import('../measuring');
      ({
        calculateGifWidthAndHeight,
        calculateTextHeight,
        CHATS_SPACING,
        getAttachmentsHeight,
        getMessageMargins,
        getReactionsHeight,
        getReactionsLines,
        getEstimatedMessageSize,
        MESSAGE_TEXT_MAX_WIDTH,
        MESSAGE_TEXT_STYLES,
      } = measuring);
      ({getReactionText} =
        require('../reactions') as typeof import('../reactions'));
    });
  });

  it('calculates gif dimensions based on aspect ratio', () => {
    expect(calculateGifWidthAndHeight(2)).toEqual({
      width: CHATS_SPACING.gifHorizontalWidth,
      height: Math.round(CHATS_SPACING.gifHorizontalWidth / 2),
    });
    expect(calculateGifWidthAndHeight(0.5)).toEqual({
      width: CHATS_SPACING.gifVerticalWidth,
      height: Math.round(CHATS_SPACING.gifVerticalWidth / 0.5),
    });
    expect(calculateGifWidthAndHeight(1)).toEqual({
      width: CHATS_SPACING.gifSquareWidth,
      height: Math.round(CHATS_SPACING.gifSquareWidth / 1),
    });
  });

  it('formats reaction text using locale', () => {
    mockFormatNumber.mockReturnValue('1.000');

    const label = getReactionText({name: 'like', count: 1000});

    expect(mockFormatNumber).toHaveBeenCalledWith({
      value: 1000,
      lng: 'es',
      fractionDigits: 0,
      withSeparators: true,
    });
    expect(label).toBe('like 1.000');
  });

  it('calculates reactions lines and height', () => {
    expect(getReactionsLines()).toBe(0);
    expect(getReactionsLines(0)).toBe(0);

    const reactionsAmount = MAX_REACTIONS_FOR_MESSAGE - 1;
    const expectedLines = Math.ceil(
      (reactionsAmount + 1) / MAX_REACTIONS_PER_LINE,
    );

    expect(getReactionsLines(reactionsAmount)).toBe(expectedLines);
    expect(getReactionsHeight(reactionsAmount)).toBe(
      CHATS_SPACING.reactionsHeight * expectedLines + CHATS_SPACING.gap,
    );
  });

  it('returns attachment heights based on format and count', () => {
    const fileAsset: Asset = {
      ...baseAsset,
      metadata: {format: 'file'},
      url: 'https://example.com/doc.pdf',
      size: 100,
    };
    const audioAsset = createAudioAsset();
    const gifAsset: Asset = {
      ...baseAsset,
      metadata: {format: 'gif'},
      external_reference: {id: 'gif-1', origin: 'GIPHY'},
      image_height: 120,
      image_width: 160,
      image_bytes: 1000,
      is_available: true,
    };
    const imageAsset = createImageAsset();

    expect(getAttachmentsHeight([fileAsset])).toBe(CHATS_SPACING.fileHeight);
    expect(getAttachmentsHeight([audioAsset])).toBe(CHATS_SPACING.audioHeight);
    expect(getAttachmentsHeight([gifAsset])).toBe(120);
    expect(getAttachmentsHeight([imageAsset])).toBe(CHATS_SPACING.bigImageSize);
    expect(getAttachmentsHeight([imageAsset, imageAsset])).toBe(
      CHATS_SPACING.mediumImageSize,
    );
    expect(getAttachmentsHeight([imageAsset, imageAsset, imageAsset])).toBe(
      CHATS_SPACING.smallImageSize,
    );
  });

  it('returns system message height when blocks are system', async () => {
    mockIsSystemMessage.mockReturnValue(true);

    const result = await calculateTextHeight({
      blocks: [
        {type: 'channel_created'} as Block,
        {type: 'call_ended'} as Block,
      ],
    });

    expect(result).toBe(CHATS_SPACING.systemMessageHeight * 2);
    expect(mockGetLineCount).not.toHaveBeenCalled();
  });

  it('calculates text height using line count', async () => {
    mockIsSystemMessage.mockReturnValue(false);
    mockGetLineCount.mockResolvedValue([2]);

    const result = await calculateTextHeight({
      text: 'hello world',
      blocks: [],
    });

    expect(mockGetLineCount).toHaveBeenCalledWith({
      text: ['hello world'],
      width: MESSAGE_TEXT_MAX_WIDTH,
      fontSize: MESSAGE_TEXT_STYLES.fontSize,
      letterSpacing: MESSAGE_TEXT_STYLES.letterSpacing,
      fontWeight: MESSAGE_TEXT_STYLES.fontWeight,
      fontFamily: MESSAGE_TEXT_STYLES.fontFamily,
      fontStyle: MESSAGE_TEXT_STYLES.fontStyle,
      textBreakStrategy: 'simple',
      allowFontScaling: false,
      includeFontPadding: false,
      lineHeight: MESSAGE_TEXT_STYLES.lineHeight,
    });
    expect(result).toBe(2 * MESSAGE_TEXT_STYLES.lineHeight);
  });

  it('returns proper margins for date pills', () => {
    const margins = getMessageMargins({
      extendedType: ExtendedMessageType.DatePill,
    } as ExtendedMessage);

    expect(margins).toEqual({
      marginTop: CHATS_SPACING.gap,
      marginBottom: 0,
    });
  });

  it('returns proper margins for unread pills', () => {
    const margins = getMessageMargins({
      extendedType: ExtendedMessageType.UnreadPill,
    } as ExtendedMessage);

    expect(margins).toEqual({
      marginTop: CHATS_SPACING.bigGap,
      marginBottom: CHATS_SPACING.gap,
    });
  });

  it('returns list padding size for list padding items', () => {
    const size = getEstimatedMessageSize(
      {
        extendedType: ExtendedMessageType.ListPadding,
      } as ExtendedMessage,
      0,
    );

    expect(size).toBe(CHATS_SPACING.scrollPadding);
  });

  describe('getEstimatedMessageSize cases', () => {
    it('returns 0 when item is missing', () => {
      expect(
        getEstimatedMessageSize(undefined as unknown as ExtendedMessage, 0),
      ).toBe(0);
    });

    it('returns size for system messages', () => {
      const item = {
        extendedType: ExtendedMessageType.SystemMessage,
        data: {height: 40},
        useExtraMarginTop: true,
      } as ExtendedMessage;

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(40 + CHATS_SPACING.gap);
    });

    it('returns size for date pills', () => {
      const datePill = {
        extendedType: ExtendedMessageType.DatePill,
      } as ExtendedMessage;

      expect(getEstimatedMessageSize(datePill, 0)).toBe(
        CHATS_SPACING.pillTextHeight + CHATS_SPACING.gap,
      );
    });

    it('returns size for unread pills', () => {
      const unreadPill = {
        extendedType: ExtendedMessageType.UnreadPill,
      } as ExtendedMessage;

      expect(getEstimatedMessageSize(unreadPill, 0)).toBe(
        CHATS_SPACING.pillTextHeight + CHATS_SPACING.bigGap + CHATS_SPACING.gap,
      );
    });

    it('returns size for unsupported messages with sender info', () => {
      const item = {
        extendedType: ExtendedMessageType.UnsupportedMessage,
        showSenderInfo: true,
        useExtraMarginTop: true,
      } as ExtendedMessage;

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.unsupportedMessageTextHeight +
          CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.gap +
          CHATS_SPACING.usernameLineHeight +
          CHATS_SPACING.smallGap,
      );
    });

    it('returns size for deleted messages without sender info', () => {
      const item = {
        extendedType: ExtendedMessageType.DeletedMessage,
        showSenderInfo: false,
      } as ExtendedMessage;

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.deletedMessageHeight +
          CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.messageFooterTextHeight +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.smallGap,
      );
    });

    it('returns size for reactions when provided', () => {
      const item = {
        extendedType: ExtendedMessageType.Reactions,
        data: {
          clientMsgId: 'client-1',
          channel: 'channel-1',
          isSentByCurrentUser: false,
        },
      } as ExtendedMessage;

      mockGetMessageReactionsCount.mockReturnValue(1);

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(CHATS_SPACING.reactionsHeight + CHATS_SPACING.gap);
    });

    it('returns 0 for default messages with undefined or -1 height', () => {
      expect(
        getEstimatedMessageSize(
          createDefaultItem({
            data: createDefaultMessage({height: undefined}),
          }),
          0,
        ),
      ).toBe(0);
      expect(
        getEstimatedMessageSize(
          createDefaultItem({
            data: createDefaultMessage({height: -1}),
          }),
          0,
        ),
      ).toBe(0);
    });

    it('adds forwarded label height when forwarded', () => {
      mockIsMessageReplied.mockReturnValue(false);
      const item = createDefaultItem({
        data: createDefaultMessage({forwarded: true, height: 10}),
      });

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.forwardedLabelHeight +
          CHATS_SPACING.messageFooterTextHeight +
          CHATS_SPACING.smallGap +
          10,
      );
    });

    it('adds reply block size when message is replied', () => {
      mockIsMessageReplied.mockReturnValue(true);
      const item = createDefaultItem({
        data: createDefaultMessage({forwarded: false, height: 10}),
      });

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.replyHeight +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.messageFooterTextHeight +
          CHATS_SPACING.smallGap +
          10,
      );
    });

    it('uses audio height when audio attachment exists', () => {
      mockIsMessageReplied.mockReturnValue(false);
      const item = createDefaultItem({
        showSenderInfo: true,
        data: createDefaultMessage({
          height: 0,
          attachments: [createAudioAsset()],
        }),
      });

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.audioHeight +
          CHATS_SPACING.usernameLineHeight +
          CHATS_SPACING.smallGap,
      );
    });

    it('uses audio height when pending assets exist and adds error size', () => {
      mockIsMessageReplied.mockReturnValue(false);
      const item = createDefaultItem({
        data: createDefaultMessage({
          height: 0,
          pendingAssets: [createPendingAsset()],
          status: MessageStatus.Error,
        }),
      });

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.audioHeight +
          CHATS_SPACING.errorMessageHeight +
          CHATS_SPACING.gap,
      );
    });

    it('adds attachments and sender info for text messages', () => {
      mockIsMessageReplied.mockReturnValue(false);
      const item = createDefaultItem({
        showSenderInfo: true,
        data: createDefaultMessage({
          height: 12,
          attachments: [createImageAsset(), createImageAsset()],
        }),
      });

      const size = getEstimatedMessageSize(item, 0);

      expect(size).toBe(
        CHATS_SPACING.bubbleVerticalPadding * 2 +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.messageFooterTextHeight +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.mediumImageSize +
          CHATS_SPACING.smallGap +
          CHATS_SPACING.usernameLineHeight +
          CHATS_SPACING.smallGap +
          12,
      );
    });
  });
});
