/* eslint-disable @typescript-eslint/no-explicit-any */
import {ulid} from 'ulid';

// Mock DB repositories
const mockGetMessagesAboveByTs = jest.fn().mockResolvedValue([]);
const mockGetMessagesBelowByTs = jest.fn().mockResolvedValue([]);
const mockGetMessagesAroundTs = jest.fn().mockResolvedValue([]);
const mockGetMessagesWithHeight = jest.fn().mockImplementation(msgs => msgs);

// Mock services
const mockFetchMessagesAbove = jest.fn().mockResolvedValue({
  messages: [],
  hasMore: false,
});
const mockFetchMessagesBelow = jest.fn().mockResolvedValue({
  messages: [],
  hasMore: false,
});
const mockFetchMessagesAround = jest.fn().mockResolvedValue({
  messages: [],
  hasMoreAbove: false,
  hasMoreBelow: false,
});

// Mock MessageRangesHandler
const mockGet = jest.fn().mockReturnValue([]);
const mockFindRangeForTs = jest.fn().mockReturnValue(undefined);
const mockIsInLoadedRange = jest.fn().mockReturnValue(false);
const mockAddMessagesAndUpdateRanges = jest.fn();
const mockIsAtConversationStart = jest.fn().mockReturnValue(false);

const mockGetMessageRangesHandler = () => ({
  MessageRangesHandler: {
    get: mockGet,
    findRangeForTs: mockFindRangeForTs,
    isInLoadedRange: mockIsInLoadedRange,
    addMessagesAndUpdateRanges: mockAddMessagesAndUpdateRanges,
    isAtConversationStart: mockIsAtConversationStart,
  },
});

const mockGetDb = () => ({
  getMessagesAboveByTs: mockGetMessagesAboveByTs,
  getMessagesBelowByTs: mockGetMessagesBelowByTs,
  getMessagesAroundTs: mockGetMessagesAroundTs,
  getMessagesWithHeight: mockGetMessagesWithHeight,
});

const mockGetServices = () => ({
  fetchMessagesAbove: mockFetchMessagesAbove,
  fetchMessagesBelow: mockFetchMessagesBelow,
  fetchMessagesAround: mockFetchMessagesAround,
});

const mockGetChatUtils = () => ({
  mapMessageFromDB: jest.fn().mockImplementation(({message}) => ({
    ts: message.ts,
    channel: message.conversationId,
    text: message.text || '',
    assets: [],
    reactions: [],
  })),
});

// Initial mocks
jest.mock('../MessageRangesHandler', () => mockGetMessageRangesHandler());
jest.mock('@modules/chats/db', () => mockGetDb());
jest.mock('@modules/chats/services', () => mockGetServices());
jest.mock('@modules/chats/utils', () => mockGetChatUtils());

// Cut heavy @shared/utils barrel — orchestrator only uses sentryLogs.
jest.mock('@shared/utils', () => ({
  sentryLogs: {
    error: jest.fn(),
    warn: jest.fn(),
    info: jest.fn(),
    debug: jest.fn(),
  },
}));

// Helper to generate ULIDs
const generateUlid = (timestamp: number) => ulid(timestamp);

// Create mock messages
const createMockMessage = (ts: string, conversationId: string) => ({
  ts,
  channel: conversationId,
  text: `Message at ${ts}`,
  assets: [],
  reactions: [],
});

const createMockDBMessage = (ts: string, conversationId: string) => ({
  ts,
  conversationId,
  text: `Message at ${ts}`,
  assets: [],
  reactions: [],
});

// Helper to create fresh instance
const createOrchestrator = () => {
  jest.resetModules();
  jest.mock('../MessageRangesHandler', () => mockGetMessageRangesHandler());
  jest.mock('@modules/chats/db', () => mockGetDb());
  jest.mock('@modules/chats/services', () => mockGetServices());
  jest.mock('@modules/chats/utils', () => mockGetChatUtils());

  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const {MessagesOrchestrator} = require('../MessagesOrchestrator');

  return MessagesOrchestrator;
};

describe('MessagesOrchestrator', () => {
  const CHANNEL_ID = 'channel-1';

  let baseTimestamp: number;
  let MessagesOrchestrator: any;

  beforeEach(() => {
    // Clear all mocks
    mockGet.mockClear().mockReturnValue([]);
    mockFindRangeForTs.mockClear().mockReturnValue(undefined);
    mockIsInLoadedRange.mockClear().mockReturnValue(false);
    mockAddMessagesAndUpdateRanges.mockClear();
    mockIsAtConversationStart.mockClear().mockReturnValue(false);

    mockGetMessagesAboveByTs.mockClear().mockResolvedValue([]);
    mockGetMessagesBelowByTs.mockClear().mockResolvedValue([]);
    mockGetMessagesAroundTs.mockClear().mockResolvedValue([]);
    mockFetchMessagesAbove.mockClear().mockResolvedValue({
      messages: [],
      hasMore: false,
    });
    mockFetchMessagesBelow.mockClear().mockResolvedValue({
      messages: [],
      hasMore: false,
    });
    mockFetchMessagesAround.mockClear().mockResolvedValue({
      messages: [],
      hasMoreAbove: false,
      hasMoreBelow: false,
    });
    mockGetMessagesWithHeight.mockClear().mockImplementation(msgs => msgs);

    // Fresh instance
    MessagesOrchestrator = createOrchestrator();

    // Base timestamp
    baseTimestamp = Date.now() - 100000;
  });

  describe('loadMessagesAbove', () => {
    describe('when no range exists', () => {
      it('should fetch from server when no range contains the anchor', async () => {
        const anchorTs = generateUlid(baseTimestamp + 50000);
        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp), CHANNEL_ID),
          createMockMessage(generateUlid(baseTimestamp + 10000), CHANNEL_ID),
        ];

        // No range contains the anchor
        mockFindRangeForTs.mockReturnValue(undefined);

        mockFetchMessagesAbove.mockResolvedValue({
          messages: serverMessages,
          hasMore: false,
        });

        const result = await MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          anchorTs,
        );

        expect(mockFetchMessagesAbove).toHaveBeenCalledWith({
          groupId: CHANNEL_ID,
          firstEventTs: anchorTs,
          limit: expect.any(Number),
        });
        expect(result.source).toBe('server');
        expect(result.messages).toEqual(serverMessages);
        expect(result.hasMore).toBe(false);
      });
    });

    describe('when range exists', () => {
      it('should load from DB when messages are within range', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts2 = generateUlid(baseTimestamp + 10000);
        const ts3 = generateUlid(baseTimestamp + 20000);
        const anchorTs = ts3;

        // Mock range containing the anchor
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts3,
          isStart: true,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(true);

        // Mock DB response
        mockGetMessagesAboveByTs.mockResolvedValue([
          createMockDBMessage(ts1, CHANNEL_ID),
          createMockDBMessage(ts2, CHANNEL_ID),
        ]);

        const result = await MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          anchorTs,
        );

        expect(result.source).toBe('db');
        expect(result.messages).toHaveLength(2);
        expect(mockFetchMessagesAbove).not.toHaveBeenCalled();
      });

      it('should return empty when at conversation start and anchor is at oldest', async () => {
        const ts1 = generateUlid(baseTimestamp);

        // Mock range where anchor is at the start
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts1,
          isStart: true,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(true);

        // DB returns empty (no messages above)
        mockGetMessagesAboveByTs.mockResolvedValue([]);

        const result = await MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          ts1,
        );

        expect(result.messages).toHaveLength(0);
        expect(result.hasMore).toBe(false);
        expect(result.source).toBe('db');
      });

      it('should fetch from server when at range boundary without isStart', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts2 = generateUlid(baseTimestamp + 10000);

        // Mock range NOT marked as start
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts2,
          isStart: false,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(false);

        // DB returns empty (at boundary)
        mockGetMessagesAboveByTs.mockResolvedValue([]);

        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp - 10000), CHANNEL_ID),
        ];

        mockFetchMessagesAbove.mockResolvedValue({
          messages: serverMessages,
          hasMore: false,
        });

        const result = await MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          ts1,
        );

        expect(mockFetchMessagesAbove).toHaveBeenCalled();
        expect(result.source).toBe('server');
      });

      it('should fetch from server when DB returns less than limit and not at start', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts3 = generateUlid(baseTimestamp + 20000);
        const anchorTs = ts3;

        // Mock range NOT marked as start
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts3,
          isStart: false,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(false);

        // DB returns less than limit
        mockGetMessagesAboveByTs.mockResolvedValue([
          createMockDBMessage(ts1, CHANNEL_ID),
        ]);

        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp - 10000), CHANNEL_ID),
        ];

        mockFetchMessagesAbove.mockResolvedValue({
          messages: serverMessages,
          hasMore: false,
        });

        const result = await MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          anchorTs,
          {limit: 10},
        );

        expect(result.source).toBe('mixed');
        expect(result.messages.length).toBeGreaterThan(1);
      });
    });
  });

  describe('loadMessagesBelow', () => {
    describe('when no range exists', () => {
      it('should fetch from server when no range contains the anchor', async () => {
        const anchorTs = generateUlid(baseTimestamp);
        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp + 10000), CHANNEL_ID),
          createMockMessage(generateUlid(baseTimestamp + 20000), CHANNEL_ID),
        ];

        // No range contains the anchor
        mockFindRangeForTs.mockReturnValue(undefined);

        mockFetchMessagesBelow.mockResolvedValue({
          messages: serverMessages,
          hasMore: false,
        });

        const result = await MessagesOrchestrator.loadMessagesBelow(
          CHANNEL_ID,
          anchorTs,
        );

        expect(mockFetchMessagesBelow).toHaveBeenCalled();
        expect(result.source).toBe('server');
        expect(result.messages).toEqual(serverMessages);
      });
    });

    describe('when range exists', () => {
      it('should load from DB when messages are within range', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts2 = generateUlid(baseTimestamp + 10000);
        const ts3 = generateUlid(baseTimestamp + 20000);
        const anchorTs = ts1;

        // Mock range containing the anchor
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts3,
          isStart: true,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);

        // Mock DB response
        mockGetMessagesBelowByTs.mockResolvedValue([
          createMockDBMessage(ts2, CHANNEL_ID),
          createMockDBMessage(ts3, CHANNEL_ID),
        ]);

        const result = await MessagesOrchestrator.loadMessagesBelow(
          CHANNEL_ID,
          anchorTs,
        );

        expect(result.source).toBe('db');
        expect(result.messages).toHaveLength(2);
        expect(mockFetchMessagesBelow).not.toHaveBeenCalled();
      });

      it('should return empty when at live edge (isEnd)', async () => {
        const ts1 = generateUlid(baseTimestamp);

        // Mock range marked as end, anchor at newest
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts1,
          isStart: true,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);

        // DB returns empty (no messages below)
        mockGetMessagesBelowByTs.mockResolvedValue([]);

        const result = await MessagesOrchestrator.loadMessagesBelow(
          CHANNEL_ID,
          ts1,
        );

        expect(result.messages).toHaveLength(0);
        expect(result.hasMore).toBe(false);
        expect(result.source).toBe('db');
      });

      it('should fetch from server when at range boundary without isEnd', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts2 = generateUlid(baseTimestamp + 10000);

        // Mock range NOT marked as end
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts2,
          isStart: true,
          isEnd: false,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);

        // DB returns empty (at boundary)
        mockGetMessagesBelowByTs.mockResolvedValue([]);

        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp + 20000), CHANNEL_ID),
        ];

        mockFetchMessagesBelow.mockResolvedValue({
          messages: serverMessages,
          hasMore: true,
        });

        const result = await MessagesOrchestrator.loadMessagesBelow(
          CHANNEL_ID,
          ts2,
        );

        expect(mockFetchMessagesBelow).toHaveBeenCalled();
        expect(result.source).toBe('server');
      });
    });
  });

  describe('loadMessagesAroundTs', () => {
    describe('when target is not in any range', () => {
      it('should fetch from server', async () => {
        const targetTs = generateUlid(baseTimestamp + 50000);
        const serverMessages = [
          createMockMessage(generateUlid(baseTimestamp + 40000), CHANNEL_ID),
          createMockMessage(targetTs, CHANNEL_ID),
          createMockMessage(generateUlid(baseTimestamp + 60000), CHANNEL_ID),
        ];

        // No range contains the target
        mockFindRangeForTs.mockReturnValue(undefined);

        mockFetchMessagesAround.mockResolvedValue({
          messages: serverMessages,
          hasMoreAbove: false,
          hasMoreBelow: false,
        });

        const result = await MessagesOrchestrator.loadMessagesAroundTs(
          CHANNEL_ID,
          targetTs,
        );

        expect(mockFetchMessagesAround).toHaveBeenCalledWith({
          groupId: CHANNEL_ID,
          targetTs,
          limit: expect.any(Number),
        });
        expect(result.source).toBe('server');
      });
    });

    describe('when target is in range', () => {
      it('should load from DB when enough messages around target', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const ts2 = generateUlid(baseTimestamp + 10000);
        const targetTs = generateUlid(baseTimestamp + 20000);
        const ts4 = generateUlid(baseTimestamp + 30000);
        const ts5 = generateUlid(baseTimestamp + 40000);

        // Mock range covering target with isStart and isEnd
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts5,
          isStart: true,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(true);

        // Mock DB response with messages around target
        mockGetMessagesAroundTs.mockResolvedValue([
          createMockDBMessage(ts1, CHANNEL_ID),
          createMockDBMessage(ts2, CHANNEL_ID),
          createMockDBMessage(targetTs, CHANNEL_ID),
          createMockDBMessage(ts4, CHANNEL_ID),
          createMockDBMessage(ts5, CHANNEL_ID),
        ]);

        const result = await MessagesOrchestrator.loadMessagesAroundTs(
          CHANNEL_ID,
          targetTs,
          {limitBefore: 2, limitAfter: 2},
        );

        expect(result.source).toBe('db');
        expect(mockFetchMessagesAround).not.toHaveBeenCalled();
      });

      it('should fetch from server when not enough messages before target', async () => {
        const ts1 = generateUlid(baseTimestamp);
        const targetTs = generateUlid(baseTimestamp + 10000);
        const ts3 = generateUlid(baseTimestamp + 20000);

        // Mock range NOT marked as start
        const mockRange = {
          oldestTs: ts1,
          newestTs: ts3,
          isStart: false,
          isEnd: true,
        };
        mockFindRangeForTs.mockReturnValue(mockRange);
        mockIsAtConversationStart.mockReturnValue(false);

        // DB returns only 1 message before target, but we need more
        mockGetMessagesAroundTs.mockResolvedValue([
          createMockDBMessage(ts1, CHANNEL_ID),
          createMockDBMessage(targetTs, CHANNEL_ID),
          createMockDBMessage(ts3, CHANNEL_ID),
        ]);

        mockFetchMessagesAround.mockResolvedValue({
          messages: [
            createMockMessage(generateUlid(baseTimestamp - 20000), CHANNEL_ID),
            createMockMessage(generateUlid(baseTimestamp - 10000), CHANNEL_ID),
            createMockMessage(targetTs, CHANNEL_ID),
            createMockMessage(ts3, CHANNEL_ID),
          ],
          hasMoreAbove: false,
          hasMoreBelow: false,
        });

        const result = await MessagesOrchestrator.loadMessagesAroundTs(
          CHANNEL_ID,
          targetTs,
          {limitBefore: 5, limitAfter: 2},
        );

        expect(mockFetchMessagesAround).toHaveBeenCalled();
        expect(result.source).toBe('server');
      });
    });
  });

  describe('handleNewMessageFromSocket', () => {
    it('should call addMessagesAndUpdateRanges with bridgeOldestTs when message is not in range', () => {
      const ts1 = generateUlid(baseTimestamp);
      const ts2 = generateUlid(baseTimestamp + 10000);

      // Mock existing range with isEnd: true (live edge)
      mockGet.mockReturnValue([
        {oldestTs: ts1, newestTs: ts2, isStart: true, isEnd: true},
      ]);

      // Message is not in any range
      mockIsInLoadedRange.mockReturnValue(false);

      // New message from socket
      const newTs = generateUlid(baseTimestamp + 20000);
      const newMessage = createMockMessage(newTs, CHANNEL_ID);

      MessagesOrchestrator.handleNewMessageFromSocket(CHANNEL_ID, newMessage);

      expect(mockAddMessagesAndUpdateRanges).toHaveBeenCalledWith(
        CHANNEL_ID,
        [newMessage],
        {isStart: false, isEnd: true, bridgeOldestTs: ts2},
      );
    });

    it('should skip if message is already in range', () => {
      const ts1 = generateUlid(baseTimestamp);
      const ts2 = generateUlid(baseTimestamp + 10000);

      // Mock existing range
      mockGet.mockReturnValue([
        {oldestTs: ts1, newestTs: ts2, isStart: true, isEnd: true},
      ]);

      // Message IS in range
      mockIsInLoadedRange.mockReturnValue(true);

      // Try to add message already in range
      const message = createMockMessage(ts1, CHANNEL_ID);

      MessagesOrchestrator.handleNewMessageFromSocket(CHANNEL_ID, message);

      // Should NOT call addMessagesAndUpdateRanges
      expect(mockAddMessagesAndUpdateRanges).not.toHaveBeenCalled();
    });

    it('should create first range when no ranges exist (no bridgeOldestTs)', () => {
      // No ranges exist
      mockGet.mockReturnValue([]);
      mockIsInLoadedRange.mockReturnValue(false);

      const newTs = generateUlid(baseTimestamp);
      const newMessage = createMockMessage(newTs, CHANNEL_ID);

      MessagesOrchestrator.handleNewMessageFromSocket(CHANNEL_ID, newMessage);

      expect(mockAddMessagesAndUpdateRanges).toHaveBeenCalledWith(
        CHANNEL_ID,
        [newMessage],
        {isStart: false, isEnd: true, bridgeOldestTs: undefined},
      );
    });
  });

  describe('Jump to Reply Scenarios', () => {
    it('should fetch from server when jumping to ts not in any range', async () => {
      const liveTs1 = generateUlid(baseTimestamp + 90000);
      const liveTs2 = generateUlid(baseTimestamp + 100000);

      // Mock existing range (live edge)
      mockGet.mockReturnValue([
        {oldestTs: liveTs1, newestTs: liveTs2, isStart: false, isEnd: true},
      ]);

      // Reply ts is NOT in range
      const replyTs = generateUlid(baseTimestamp);
      mockFindRangeForTs.mockReturnValue(undefined);

      const aroundMessages = [
        createMockMessage(generateUlid(baseTimestamp - 10000), CHANNEL_ID),
        createMockMessage(replyTs, CHANNEL_ID),
        createMockMessage(generateUlid(baseTimestamp + 10000), CHANNEL_ID),
      ];

      mockFetchMessagesAround.mockResolvedValue({
        messages: aroundMessages,
        hasMoreAbove: true,
        hasMoreBelow: true,
      });

      const result = await MessagesOrchestrator.loadMessagesAroundTs(
        CHANNEL_ID,
        replyTs,
      );

      expect(mockFetchMessagesAround).toHaveBeenCalled();
      expect(result.source).toBe('server');
      // addMessagesAndUpdateRanges should be called to create new range
      expect(mockAddMessagesAndUpdateRanges).toHaveBeenCalled();
    });

    it('should call addMessagesAndUpdateRanges with bridge params when filling gap', async () => {
      const oldTs2 = generateUlid(baseTimestamp + 10000);
      const newTs1 = generateUlid(baseTimestamp + 50000);

      // Mock existing range
      const mockRange = {
        oldestTs: oldTs2,
        newestTs: newTs1,
        isStart: false,
        isEnd: false,
      };
      mockFindRangeForTs.mockReturnValue(mockRange);

      const gapTs = generateUlid(baseTimestamp + 30000);
      const aroundMessages = [
        createMockMessage(generateUlid(baseTimestamp + 20000), CHANNEL_ID),
        createMockMessage(gapTs, CHANNEL_ID),
        createMockMessage(generateUlid(baseTimestamp + 40000), CHANNEL_ID),
      ];

      // DB returns enough messages
      mockGetMessagesAroundTs.mockResolvedValue(
        aroundMessages.map(m => createMockDBMessage(m.ts, CHANNEL_ID)),
      );

      const result = await MessagesOrchestrator.loadMessagesAroundTs(
        CHANNEL_ID,
        gapTs,
        {limitBefore: 1, limitAfter: 1},
      );

      expect(result.source).toBe('db');
    });
  });

  describe('Pagination Edge Cases', () => {
    it('should handle loading above when exactly at range boundary', async () => {
      const ts1 = generateUlid(baseTimestamp);
      const ts2 = generateUlid(baseTimestamp + 10000);

      // Mock range without isStart
      const mockRange = {
        oldestTs: ts1,
        newestTs: ts2,
        isStart: false,
        isEnd: true,
      };
      mockFindRangeForTs.mockReturnValue(mockRange);
      mockIsAtConversationStart.mockReturnValue(false);

      // DB returns empty (at boundary)
      mockGetMessagesAboveByTs.mockResolvedValue([]);

      const serverMessages = [
        createMockMessage(generateUlid(baseTimestamp - 20000), CHANNEL_ID),
        createMockMessage(generateUlid(baseTimestamp - 10000), CHANNEL_ID),
      ];

      mockFetchMessagesAbove.mockResolvedValue({
        messages: serverMessages,
        hasMore: false,
      });

      const result = await MessagesOrchestrator.loadMessagesAbove(
        CHANNEL_ID,
        ts1,
      );

      expect(mockFetchMessagesAbove).toHaveBeenCalled();
      expect(result.hasMore).toBe(false);
    });

    it('should handle loading below when exactly at range boundary', async () => {
      const ts1 = generateUlid(baseTimestamp);
      const ts2 = generateUlid(baseTimestamp + 10000);

      // Mock range without isEnd
      const mockRange = {
        oldestTs: ts1,
        newestTs: ts2,
        isStart: true,
        isEnd: false,
      };
      mockFindRangeForTs.mockReturnValue(mockRange);

      // DB returns empty (at boundary)
      mockGetMessagesBelowByTs.mockResolvedValue([]);

      const serverMessages = [
        createMockMessage(generateUlid(baseTimestamp + 20000), CHANNEL_ID),
        createMockMessage(generateUlid(baseTimestamp + 30000), CHANNEL_ID),
      ];

      mockFetchMessagesBelow.mockResolvedValue({
        messages: serverMessages,
        hasMore: false,
      });

      const result = await MessagesOrchestrator.loadMessagesBelow(
        CHANNEL_ID,
        ts2,
      );

      expect(mockFetchMessagesBelow).toHaveBeenCalled();
      expect(result.hasMore).toBe(false);
    });

    it('should propagate server errors to the caller', async () => {
      // No range found
      mockFindRangeForTs.mockReturnValue(undefined);

      mockFetchMessagesAbove.mockRejectedValue(new Error('Network error'));

      await expect(
        MessagesOrchestrator.loadMessagesAbove(
          CHANNEL_ID,
          generateUlid(baseTimestamp),
        ),
      ).rejects.toThrow('Network error');
    });
  });

  describe('Multiple Ranges Interaction', () => {
    it('should use correct range boundary when loading from middle range', async () => {
      const range2Ts1 = generateUlid(baseTimestamp + 50000);
      const range2Ts2 = generateUlid(baseTimestamp + 60000);

      // Mock that we're in range 2
      const mockRange = {
        oldestTs: range2Ts1,
        newestTs: range2Ts2,
        isStart: false,
        isEnd: false,
      };
      mockFindRangeForTs.mockReturnValue(mockRange);
      mockIsAtConversationStart.mockReturnValue(false);

      // Load from middle range
      mockGetMessagesAboveByTs.mockResolvedValue([
        createMockDBMessage(range2Ts1, CHANNEL_ID),
      ]);

      await MessagesOrchestrator.loadMessagesAbove(CHANNEL_ID, range2Ts2);

      // Should have queried with range2's minTs boundary
      expect(mockGetMessagesAboveByTs).toHaveBeenCalledWith(
        expect.objectContaining({
          minTs: range2Ts1,
        }),
      );
    });

    it('should handle query in gap between ranges (fetch from server)', async () => {
      // Query in gap - no range contains it
      const gapTs = generateUlid(baseTimestamp + 30000);
      mockFindRangeForTs.mockReturnValue(undefined);

      const serverMessages = [
        createMockMessage(generateUlid(baseTimestamp + 25000), CHANNEL_ID),
        createMockMessage(gapTs, CHANNEL_ID),
        createMockMessage(generateUlid(baseTimestamp + 35000), CHANNEL_ID),
      ];

      mockFetchMessagesAround.mockResolvedValue({
        messages: serverMessages,
        hasMoreAbove: true,
        hasMoreBelow: true,
      });

      const result = await MessagesOrchestrator.loadMessagesAroundTs(
        CHANNEL_ID,
        gapTs,
      );

      // Should fetch from server since gap is not in any range
      expect(mockFetchMessagesAround).toHaveBeenCalled();
      expect(result.source).toBe('server');
    });
  });
});
