// ─── Constants ──────────────────────────────────────────────────────────────
import type {Message} from '@modules/chats/interfaces';

// Cut heavy transitives on first module evaluation. createHandler() re-applies
// richer mocks with test-controlled behavior after jest.resetModules().
jest.mock('@shared/utils', () => ({
  sentryLogs: {
    error: jest.fn(),
    warn: jest.fn(),
    info: jest.fn(),
    debug: jest.fn(),
  },
}));
jest.mock('@modules/chats/db', () => ({}));
jest.mock('@modules/chats/services', () => ({}));
jest.mock('@modules/chats/utils', () => ({}));
jest.mock('../MessagesOrchestrator', () => ({}));

const USER_ID = 'user-123';
const CHANNEL_1 = 'channel-1';
const CHANNEL_2 = 'channel-2';

// ─── DB message factory ─────────────────────────────────────────────────────
type CreateDbMessageOverrides = Partial<{
  id: string;
  conversation_id: string;
  sender_name: string;
  formatted_text: string | null;
}>;

const createDbMessage = (
  ts: string,
  text: string,
  opts: {userId: number} & CreateDbMessageOverrides = {userId: 0},
) => ({
  id: opts.id ?? `msg-${ts}`,
  conversation_id: opts.conversation_id ?? CHANNEL_1,
  user_id: opts.userId,
  text,
  ts,
  formatted_text: opts.formatted_text ?? null,
  sender_name: opts.sender_name ?? `User ${opts.userId}`,
  sender_profile_picture: null,
  created: 0,
  updated: null,
  deleted_from_ui: false,
  latest_event_ts: null,
  status: null,
  read_at: null,
  received_at: null,
  blocks: null,
  version: 0,
  height: -1,
  associated_font_scale: -1,
  edited: false,
  hidden: false,
  highlighted_spans: null,
  assets: [],
});

type DbMessage = ReturnType<typeof createDbMessage>;

const dbMessageToMessageEvent = (db: DbMessage) => ({
  ts: db.ts,
  text: db.text ?? '',
  type: 'message',
  user: String(db.user_id),
  channel: db.conversation_id,
  username: db.sender_name,
  client_msg_id: db.id,
  attachments: [],
  hu_data: {
    message_ts: db.ts,
    version: db.version,
    highlighted_spans: [],
    sender_id: db.user_id,
    membership: {},
  },
});

const dbMessagesToApiItems = (channel: string, ...dbMessages: DbMessage[]) => ({
  ok: true as const,
  items: dbMessages.map(m => ({
    channel,
    created: 0,
    created_by: String(m.user_id),
    message: dbMessageToMessageEvent(m),
    type: 'message',
  })),
});

const createMessage = (ts: string, channel = CHANNEL_1, text = '') => ({
  ts,
  text,
  user: '',
  username: '',
  channel,
  client_msg_id: ts,
  version: 0,
  height: -1,
  highlighted_spans: [] as never[],
  formatted_text: text,
});

const dbToMappedMessage = (db: DbMessage, channel?: string) => ({
  ts: db.ts,
  text: db.text ?? '',
  user: String(db.user_id),
  username: db.sender_name,
  channel: channel ?? db.conversation_id,
  client_msg_id: db.id,
  version: db.version ?? 0,
  height: db.height ?? -1,
  highlighted_spans: [],
  formatted_text: db.formatted_text ?? db.text ?? '',
  attachments: [],
});

// ─── In-memory fake DB ──────────────────────────────────────────────────────
let mockPinnedDb: Record<string, DbMessage[]> = {};
let mockMessagesDb: DbMessage[] = [];

// ─── Jest fns ───────────────────────────────────────────────────────────────
const mockGetPinnedMessages = jest.fn();
const mockAddPinnedMessage = jest.fn();
const mockRemovePinnedMessage = jest.fn();
const mockLoadMessagesAroundTs = jest.fn();
const mockGetMessageByTsWithAssets = jest.fn();
const mockMapMessageFromServer = jest.fn();
const mockMapMessageFromDB = jest.fn();

const setupDefaultServiceMocks = () => {
  mockGetPinnedMessages.mockImplementation(({channel}: {channel: string}) => {
    const items = (mockPinnedDb[channel] ?? []).map(m => ({
      channel,
      created: 0,
      created_by: String(m.user_id),
      message: dbMessageToMessageEvent(m),
      type: 'message',
    }));
    return Promise.resolve({ok: true, items});
  });

  mockAddPinnedMessage.mockImplementation(
    ({channel, ts}: {channel: string; ts: string}) => {
      if (!mockPinnedDb[channel]) {
        mockPinnedDb[channel] = [];
      }
      if (!mockPinnedDb[channel].some(m => m.ts === ts)) {
        const stub = createDbMessage(ts, '', {
          userId: 0,
          conversation_id: channel,
        });
        mockPinnedDb[channel].push(stub);
        mockMessagesDb.push(stub);
      }
      const items = mockPinnedDb[channel].map(m => ({
        channel,
        created: 0,
        created_by: String(m.user_id),
        message: dbMessageToMessageEvent(m),
        type: 'message',
      }));
      return Promise.resolve({ok: true, items});
    },
  );

  mockRemovePinnedMessage.mockImplementation(
    ({channel, ts}: {channel: string; ts: string}) => {
      if (mockPinnedDb[channel]) {
        mockPinnedDb[channel] = mockPinnedDb[channel].filter(m => m.ts !== ts);
      }
      const items = (mockPinnedDb[channel] ?? []).map(m => ({
        channel,
        created: 0,
        created_by: String(m.user_id),
        message: dbMessageToMessageEvent(m),
        type: 'message',
      }));
      return Promise.resolve({ok: true, items});
    },
  );

  mockGetMessageByTsWithAssets.mockImplementation((ts: string) => {
    const found = mockMessagesDb.find(m => m.ts === ts);
    return Promise.resolve(
      found ? {...found, assets: found.assets ?? []} : null,
    );
  });

  mockMapMessageFromServer.mockImplementation(
    (event: {
      ts: string;
      text?: string;
      channel: string;
      client_msg_id?: string;
      username?: string;
      hu_data?: {sender_id?: number};
    }) =>
      Promise.resolve(
        dbToMappedMessage(
          mockMessagesDb.find(m => m.ts === event.ts) ??
            createDbMessage(event.ts, event.text ?? '', {
              userId: event.hu_data?.sender_id ?? 0,
              id: event.client_msg_id,
              conversation_id: event.channel,
              sender_name: event.username,
            }),
          event.channel,
        ),
      ),
  );

  mockMapMessageFromDB.mockImplementation(({message}: {message: DbMessage}) =>
    dbToMappedMessage(message),
  );
};

// ─── Module mocks ───────────────────────────────────────────────────────────
const mockGetServices = () => ({
  getPinnedMessages: (...args: unknown[]) =>
    mockGetPinnedMessages(...(args as [object])),
  addPinnedMessage: (...args: unknown[]) =>
    mockAddPinnedMessage(...(args as [object])),
  removePinnedMessage: (...args: unknown[]) =>
    mockRemovePinnedMessage(...(args as [object])),
  PinRaceConditionError: class PinRaceConditionError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'PinRaceConditionError';
    }
  },
});

const mockGetOrchestrator = () => ({
  MessagesOrchestrator: {
    loadMessagesAroundTs: (...args: unknown[]) =>
      mockLoadMessagesAroundTs(...(args as [string, string])),
  },
});

const mockGetDb = () => ({
  getMessageByTsWithAssets: (...args: unknown[]) =>
    mockGetMessageByTsWithAssets(...(args as [string])),
});

const mockGetUtils = () => ({
  mapMessageFromServer: (...args: unknown[]) =>
    mockMapMessageFromServer(...(args as [object])),
  mapMessageFromDB: (...args: unknown[]) =>
    mockMapMessageFromDB(...(args as [object])),
  getExpiresAtForPinDuration: (duration: string) => {
    if (duration === 'always') {
      return new Date('2500-01-01T00:00:00.000Z').getTime().toString();
    }
    return new Date('2025-01-01T00:00:00.000Z').getTime().toString();
  },
});

let mockStore: Record<string, string> = {};

const mockGetMmkvFactory = () => ({
  Storage: class MockStorage {
    getString(key: string) {
      return mockStore[key];
    }
    set(key: string, value: string | number | boolean) {
      mockStore[key] = String(value);
    }
    remove(key: string) {
      delete mockStore[key];
    }
  },
});

const mockGetSharedUtils = () => ({
  sentryLogs: {
    warn: jest.fn(),
    error: jest.fn(),
    info: jest.fn(),
  },
});

type PinnedMessagesUpdaterServiceType =
  typeof import('../updaters/PinnedMessagesUpdaterService')['PinnedMessagesUpdaterService'];

const createHandler = (): PinnedMessagesUpdaterServiceType => {
  jest.resetModules();
  jest.mock('@modules/chats/services', () => mockGetServices());
  jest.mock('@modules/chats/db', () => mockGetDb());
  jest.mock('@modules/chats/utils', () => mockGetUtils());
  jest.mock('../MessagesOrchestrator', () => mockGetOrchestrator());
  jest.mock('@services/mmkvFactory', () => mockGetMmkvFactory());
  jest.mock('@shared/utils', () => mockGetSharedUtils());

  // eslint-disable-next-line @typescript-eslint/no-var-requires -- dynamic require after jest.resetModules()
  return require('../updaters/PinnedMessagesUpdaterService')
    .PinnedMessagesUpdaterService;
};

// ─── Test helpers ───────────────────────────────────────────────────────────

const setupChannelWithPins = async (
  handler: PinnedMessagesUpdaterServiceType,
  channel: string,
  ...dbMessages: DbMessage[]
) => {
  dbMessages.forEach(m => {
    if (!mockPinnedDb[channel]) {
      mockPinnedDb[channel] = [];
    }
    mockPinnedDb[channel].push(m);
    if (!mockMessagesDb.some(existing => existing.ts === m.ts)) {
      mockMessagesDb.push(m);
    }
  });
  await handler.getPinnedMessages(channel);
};

describe('PinnedMessagesUpdaterService', () => {
  let handler: PinnedMessagesUpdaterServiceType;

  beforeEach(() => {
    mockStore = {};
    mockPinnedDb = {};
    mockMessagesDb = [];

    mockGetPinnedMessages.mockReset();
    mockAddPinnedMessage.mockReset();
    mockRemovePinnedMessage.mockReset();
    mockLoadMessagesAroundTs.mockReset();
    mockGetMessageByTsWithAssets.mockReset();
    mockMapMessageFromServer.mockReset();
    mockMapMessageFromDB.mockReset();

    setupDefaultServiceMocks();
    mockLoadMessagesAroundTs.mockResolvedValue({
      messages: [],
      hasMore: false,
      source: 'db',
    });

    handler = createHandler();
    handler.setUserId(USER_ID);
    // eslint-disable-next-line @typescript-eslint/no-var-requires -- access store after resetModules
    require('@modules/chats/store/zustand/usePinnedMessages')
      .usePinnedMessages.getState()
      .clear();
  });

  // ─── setUserId ──────────────────────────────────────────────────────────

  describe('setUserId', () => {
    it('should return empty from cache when no data exists', () => {
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should use different storage keys per user', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01AAA', 'Hi', {userId: 1}),
        createDbMessage('01BBB', 'Bye', {userId: 2}),
      );

      expect(mockStore).toHaveProperty(`${USER_ID}:${CHANNEL_1}`);

      handler.setUserId('other-user');
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should clear in-memory cache when user changes', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01XXX', 'Text', {userId: 1}),
      );
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);

      handler.setUserId('different-user');
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(0);
    });

    it('should be a no-op when setting the same userId', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01NOOP', 'NoOp', {userId: 1}),
      );
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);

      handler.setUserId(USER_ID);

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);
    });
  });

  // ─── getPinnedMessages ──────────────────────────────────────────────────

  describe('getPinnedMessages', () => {
    it('should call API, persist to storage and update cache', async () => {
      const dbMessages = [
        createDbMessage('01A', 'One', {userId: 1}),
        createDbMessage('01B', 'Two', {userId: 2}),
      ];
      mockGetPinnedMessages.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, ...dbMessages),
      );

      await handler.getPinnedMessages(CHANNEL_1);

      expect(mockGetPinnedMessages).toHaveBeenCalledWith({channel: CHANNEL_1});
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(2);
      expect(JSON.parse(mockStore[`${USER_ID}:${CHANNEL_1}`] ?? '[]')).toEqual([
        '01A',
        '01B',
      ]);
    });

    it('should call mapMessageFromServer for each event', async () => {
      mockGetPinnedMessages.mockResolvedValue(
        dbMessagesToApiItems(
          CHANNEL_1,
          createDbMessage('01A', 'One', {userId: 1}),
        ),
      );

      await handler.getPinnedMessages(CHANNEL_1);

      expect(mockMapMessageFromServer).toHaveBeenCalledTimes(1);
      expect(mockMapMessageFromServer.mock.calls[0][0]).toMatchObject({
        ts: '01A',
      });
    });

    it('should not throw when API fails', async () => {
      mockGetPinnedMessages.mockRejectedValue(new Error('API error'));
      await handler.getPinnedMessages(CHANNEL_1);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should set empty cache when API returns no items', async () => {
      mockGetPinnedMessages.mockResolvedValue(dbMessagesToApiItems(CHANNEL_1));
      await handler.getPinnedMessages(CHANNEL_1);
      mockGetPinnedMessages.mockResolvedValue({ok: true});
      await handler.getPinnedMessages(CHANNEL_1);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should trigger loadMessagesAroundTs for each pinned ts', async () => {
      const dbMessages = [
        createDbMessage('01A', 'One', {userId: 1}),
        createDbMessage('01B', 'Two', {userId: 2}),
      ];
      mockGetPinnedMessages.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, ...dbMessages),
      );

      await handler.getPinnedMessages(CHANNEL_1);

      expect(mockLoadMessagesAroundTs).toHaveBeenCalledWith(CHANNEL_1, '01A');
      expect(mockLoadMessagesAroundTs).toHaveBeenCalledWith(CHANNEL_1, '01B');
      expect(mockLoadMessagesAroundTs).toHaveBeenCalledTimes(2);
    });
  });

  // ─── getCachedPinnedMessages ────────────────────────────────────────────

  describe('getCachedPinnedMessages', () => {
    it('should return empty when nothing stored', () => {
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should return persisted data after getPinnedMessages', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01A', 'One', {userId: 1}),
        createDbMessage('01B', 'Two', {userId: 2}),
      );

      const cached = handler.getCachedPinnedMessages(CHANNEL_1);
      expect(cached).toHaveLength(2);
      expect(cached[0]).toMatchObject({ts: '01A', text: 'One', user: '1'});
      expect(cached[1]).toMatchObject({ts: '01B', text: 'Two', user: '2'});
    });

    it('should keep channels separate', async () => {
      mockGetPinnedMessages
        .mockResolvedValueOnce(
          dbMessagesToApiItems(
            CHANNEL_1,
            createDbMessage('01A', 'Ch1', {userId: 1}),
          ),
        )
        .mockResolvedValueOnce(
          dbMessagesToApiItems(
            CHANNEL_2,
            createDbMessage('01B', 'Ch2', {userId: 2}),
          ),
        );
      await handler.getPinnedMessages(CHANNEL_1);
      await handler.getPinnedMessages(CHANNEL_2);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);
      expect(handler.getCachedPinnedMessages(CHANNEL_2)).toHaveLength(1);
    });
  });

  // ─── loadOrFetch ────────────────────────────────────────────────────────

  describe('loadOrFetch', () => {
    it('should skip when channel is already in store', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01A', 'A', {userId: 1}),
      );
      mockGetPinnedMessages.mockClear();
      mockGetMessageByTsWithAssets.mockClear();

      await handler.loadOrFetch(CHANNEL_1);

      expect(mockGetPinnedMessages).not.toHaveBeenCalled();
      expect(mockGetMessageByTsWithAssets).not.toHaveBeenCalled();
    });

    it('should load from DB when channel is in storage', async () => {
      mockStore[`${USER_ID}:${CHANNEL_1}`] = JSON.stringify(['01A', '01B']);

      const dbA = createDbMessage('01A', 'One from DB', {userId: 1});
      const dbB = createDbMessage('01B', 'Two from DB', {userId: 2});
      mockMessagesDb.push(dbA, dbB);

      await handler.loadOrFetch(CHANNEL_1);

      expect(mockGetPinnedMessages).not.toHaveBeenCalled();
      expect(mockGetMessageByTsWithAssets).toHaveBeenCalledWith('01A');
      expect(mockGetMessageByTsWithAssets).toHaveBeenCalledWith('01B');
      expect(mockMapMessageFromDB).toHaveBeenCalledTimes(2);
      const cached = handler.getCachedPinnedMessages(CHANNEL_1);
      expect(cached).toHaveLength(2);
      expect(cached[0]).toMatchObject({
        ts: '01A',
        text: 'One from DB',
        user: '1',
      });
    });

    it('should skip DB records not found', async () => {
      mockStore[`${USER_ID}:${CHANNEL_1}`] = JSON.stringify(['01A', '01B']);

      const dbA = createDbMessage('01A', 'One from DB', {userId: 1});
      mockMessagesDb.push(dbA);
      mockGetMessageByTsWithAssets
        .mockResolvedValueOnce({...dbA, assets: []})
        .mockResolvedValueOnce(null);

      await handler.loadOrFetch(CHANNEL_1);

      const cached = handler.getCachedPinnedMessages(CHANNEL_1);
      expect(cached).toHaveLength(1);
      expect(cached[0].ts).toBe('01A');
    });

    it('should fetch from API when channel not in storage', async () => {
      const dbMessages = [
        createDbMessage('01A', 'One', {userId: 1}),
        createDbMessage('01B', 'Two', {userId: 2}),
      ];
      mockGetPinnedMessages.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, ...dbMessages),
      );

      await handler.loadOrFetch(CHANNEL_1);

      expect(mockGetPinnedMessages).toHaveBeenCalledWith({channel: CHANNEL_1});
      expect(mockMapMessageFromServer).toHaveBeenCalledTimes(2);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(2);
      expect(mockLoadMessagesAroundTs).toHaveBeenCalledWith(CHANNEL_1, '01A');
      expect(mockLoadMessagesAroundTs).toHaveBeenCalledWith(CHANNEL_1, '01B');
    });

    it('should set empty cache when API returns no items', async () => {
      mockGetPinnedMessages.mockResolvedValue(dbMessagesToApiItems(CHANNEL_1));

      await handler.loadOrFetch(CHANNEL_1);

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should set empty cache when API throws', async () => {
      mockGetPinnedMessages.mockRejectedValue(new Error('API error'));

      await handler.loadOrFetch(CHANNEL_1);

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should handle corrupted storage gracefully', async () => {
      mockStore[`${USER_ID}:${CHANNEL_1}`] = 'not valid json';
      handler.setUserId('other');
      handler.setUserId(USER_ID);

      mockGetPinnedMessages.mockResolvedValue(dbMessagesToApiItems(CHANNEL_1));
      await handler.loadOrFetch(CHANNEL_1);

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
      expect(mockStore[`${USER_ID}:${CHANNEL_1}`]).toBe('[]');
    });
  });

  // ─── add ────────────────────────────────────────────────────────────────

  describe('add', () => {
    it('should call addPinnedMessage and use returned items', async () => {
      const dbMessage = createDbMessage('01NEW', 'New pin', {userId: 1});
      mockAddPinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, dbMessage),
      );

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01NEW'),
        duration: 'always',
      });

      expect(mockAddPinnedMessage).toHaveBeenCalledWith(
        expect.objectContaining({
          channel: CHANNEL_1,
          ts: '01NEW',
          expiresAt: new Date('2500-01-01T00:00:00.000Z').getTime().toString(),
        }),
      );
      expect(mockGetPinnedMessages).not.toHaveBeenCalled();
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)[0]).toMatchObject({
        ts: '01NEW',
        text: 'New pin',
        user: '1',
      });
    });

    it('should skip if ts already in list', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01EXIST', 'Existing', {userId: 1}),
      );
      mockAddPinnedMessage.mockClear();

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01EXIST'),
        duration: 'always',
      });

      expect(mockAddPinnedMessage).not.toHaveBeenCalled();
    });

    it('should keep optimistic update when response has no items', async () => {
      mockAddPinnedMessage.mockResolvedValue({ok: true});

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01OPT', CHANNEL_1, 'Optimistic'),
        duration: 'always',
      });

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([
        expect.objectContaining({ts: '01OPT', text: 'Optimistic'}),
      ]);
      expect(mockLoadMessagesAroundTs).toHaveBeenCalledWith(CHANNEL_1, '01OPT');
    });

    it('should rollback and re-throw on failure', async () => {
      mockAddPinnedMessage.mockRejectedValue(new Error('API error'));

      await expect(
        handler.add({
          channel: CHANNEL_1,
          message: createMessage('01FAIL'),
          duration: 'always',
        }),
      ).rejects.toThrow('API error');

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
    });

    it('should pass idempotencyKey when provided', async () => {
      mockAddPinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(
          CHANNEL_1,
          createDbMessage('01K', 'K', {userId: 1}),
        ),
      );

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01K'),
        duration: 'always',
        idempotencyKey: 'my-idem-key',
      });

      expect(mockAddPinnedMessage).toHaveBeenCalledWith({
        channel: CHANNEL_1,
        ts: '01K',
        expiresAt: new Date('2500-01-01T00:00:00.000Z').getTime().toString(),
        idempotencyKey: 'my-idem-key',
      });
    });

    describe('with durations', () => {
      it.each([
        {
          duration: '24h',
          expected: new Date('2025-01-01T00:00:00.000Z').getTime().toString(),
        },
        {
          duration: '30d',
          expected: new Date('2025-01-01T00:00:00.000Z').getTime().toString(),
        },
        {
          duration: 'always',
          expected: new Date('2500-01-01T00:00:00.000Z').getTime().toString(),
        },
      ] as const)(
        'should pass correct expiresAt for $duration',
        async ({duration, expected}) => {
          mockAddPinnedMessage.mockResolvedValue(
            dbMessagesToApiItems(
              CHANNEL_1,
              createDbMessage(`01_${duration}`, `${duration} pin`, {userId: 1}),
            ),
          );

          await handler.add({
            channel: CHANNEL_1,
            message: createMessage(`01_${duration}`),
            duration,
          });

          expect(mockAddPinnedMessage).toHaveBeenCalledWith(
            expect.objectContaining({expiresAt: expected}),
          );
        },
      );
    });

    describe('with tsToReplace', () => {
      it('should replace optimistically and call only addPinnedMessage', async () => {
        const dbA = createDbMessage('01A', 'A', {userId: 1});
        const dbB = createDbMessage('01B', 'B', {userId: 2});
        await setupChannelWithPins(handler, CHANNEL_1, dbA, dbB);
        expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(2);

        const dbNew = createDbMessage('01NEW', 'New', {userId: 3});
        mockAddPinnedMessage.mockResolvedValue(
          dbMessagesToApiItems(CHANNEL_1, dbB, dbNew),
        );

        await handler.add({
          channel: CHANNEL_1,
          message: createMessage('01NEW'),
          duration: 'always',
          tsToReplace: '01A',
        });

        expect(mockRemovePinnedMessage).not.toHaveBeenCalled();
        expect(mockAddPinnedMessage).toHaveBeenCalledWith(
          expect.objectContaining({channel: CHANNEL_1, ts: '01NEW'}),
        );
        const cached = handler.getCachedPinnedMessages(CHANNEL_1);
        expect(cached).toHaveLength(2);
        expect(cached.some(m => m.ts === '01A')).toBe(false);
        expect(cached.some(m => m.ts === '01NEW')).toBe(true);
      });

      it('should rollback on failure', async () => {
        await setupChannelWithPins(
          handler,
          CHANNEL_1,
          createDbMessage('01A', 'A', {userId: 1}),
        );

        mockAddPinnedMessage.mockRejectedValue(new Error('Add failed'));

        await expect(
          handler.add({
            channel: CHANNEL_1,
            message: createMessage('01NEW'),
            duration: 'always',
            tsToReplace: '01A',
          }),
        ).rejects.toThrow('Add failed');

        const cached = handler.getCachedPinnedMessages(CHANNEL_1);
        expect(cached).toHaveLength(1);
        expect(cached[0]).toMatchObject({ts: '01A', text: 'A'});
      });

      it('should apply optimistic update before API call', async () => {
        await setupChannelWithPins(
          handler,
          CHANNEL_1,
          createDbMessage('01A', 'A', {userId: 1}),
        );

        let capturedState: Message[] = [];
        mockAddPinnedMessage.mockImplementation(() => {
          capturedState = handler.getCachedPinnedMessages(CHANNEL_1);
          return Promise.resolve(
            dbMessagesToApiItems(
              CHANNEL_1,
              createDbMessage('01NEW', 'New', {userId: 3}),
            ),
          );
        });

        await handler.add({
          channel: CHANNEL_1,
          message: createMessage('01NEW', CHANNEL_1, 'New'),
          duration: 'always',
          tsToReplace: '01A',
        });

        expect(capturedState.some(m => m.ts === '01A')).toBe(false);
        expect(capturedState.some(m => m.ts === '01NEW')).toBe(true);
      });

      it('should ignore tsToReplace when it does not exist in list', async () => {
        await setupChannelWithPins(
          handler,
          CHANNEL_1,
          createDbMessage('01A', 'A', {userId: 1}),
        );

        mockAddPinnedMessage.mockResolvedValue(
          dbMessagesToApiItems(
            CHANNEL_1,
            createDbMessage('01A', 'A', {userId: 1}),
            createDbMessage('01NEW', 'New', {userId: 2}),
          ),
        );

        await handler.add({
          channel: CHANNEL_1,
          message: createMessage('01NEW'),
          duration: 'always',
          tsToReplace: '01NONEXISTENT',
        });

        expect(mockAddPinnedMessage).toHaveBeenCalled();
        const cached = handler.getCachedPinnedMessages(CHANNEL_1);
        expect(cached).toHaveLength(2);
        expect(cached.some(m => m.ts === '01A')).toBe(true);
        expect(cached.some(m => m.ts === '01NEW')).toBe(true);
      });
    });
  });

  // ─── remove ─────────────────────────────────────────────────────────────

  describe('remove', () => {
    it('should call removePinnedMessage and use returned items', async () => {
      const dbA = createDbMessage('01A', 'A', {userId: 1});
      const dbB = createDbMessage('01B', 'B', {userId: 2});
      await setupChannelWithPins(handler, CHANNEL_1, dbA, dbB);

      mockRemovePinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, dbB),
      );

      await handler.remove({channel: CHANNEL_1, ts: '01A'});

      expect(mockRemovePinnedMessage).toHaveBeenCalledWith(
        expect.objectContaining({channel: CHANNEL_1, ts: '01A'}),
      );
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);
      expect(handler.getCachedPinnedMessages(CHANNEL_1)[0]).toMatchObject({
        ts: '01B',
        text: 'B',
        user: '2',
      });
    });

    it('should skip if ts not in list', async () => {
      mockRemovePinnedMessage.mockClear();

      await handler.remove({channel: CHANNEL_1, ts: '01NOTPRESENT'});

      expect(mockRemovePinnedMessage).not.toHaveBeenCalled();
    });

    it('should rollback and re-throw on failure', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01A', 'A', {userId: 1}),
      );
      mockRemovePinnedMessage.mockRejectedValue(new Error('API error'));

      await expect(
        handler.remove({channel: CHANNEL_1, ts: '01A'}),
      ).rejects.toThrow('API error');

      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([
        expect.objectContaining({ts: '01A', text: 'A', user: '1'}),
      ]);
    });

    it('should pass idempotencyKey when provided', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01R', 'R', {userId: 1}),
      );
      mockRemovePinnedMessage.mockResolvedValue({ok: true, items: []});

      await handler.remove({
        channel: CHANNEL_1,
        ts: '01R',
        idempotencyKey: 'remove-idem',
      });

      expect(mockRemovePinnedMessage).toHaveBeenCalledWith({
        channel: CHANNEL_1,
        ts: '01R',
        idempotencyKey: 'remove-idem',
      });
    });
  });

  // ─── idempotency and cross-operations ───────────────────────────────────

  describe('idempotency and cross-operations', () => {
    it('should not add twice for same ts', async () => {
      mockAddPinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(
          CHANNEL_1,
          createDbMessage('01DUP', 'Dup', {userId: 1}),
        ),
      );

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01DUP'),
        duration: 'always',
      });
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);

      mockAddPinnedMessage.mockClear();
      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01DUP'),
        duration: 'always',
      });

      expect(mockAddPinnedMessage).not.toHaveBeenCalled();
    });

    it('should not remove twice for same ts', async () => {
      await setupChannelWithPins(
        handler,
        CHANNEL_1,
        createDbMessage('01RM', 'Rm', {userId: 1}),
      );
      mockRemovePinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1),
      );

      await handler.remove({channel: CHANNEL_1, ts: '01RM'});
      await handler.remove({channel: CHANNEL_1, ts: '01RM'});

      expect(mockRemovePinnedMessage).toHaveBeenCalledTimes(1);
    });

    it('should add and then remove the same ts', async () => {
      const dbMessage = createDbMessage('01CROSS', 'Cross', {userId: 1});
      mockAddPinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, dbMessage),
      );

      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01CROSS'),
        duration: 'always',
      });
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);

      mockRemovePinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1),
      );

      await handler.remove({channel: CHANNEL_1, ts: '01CROSS'});
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);
      expect(mockRemovePinnedMessage).toHaveBeenCalledTimes(1);
    });

    it('should remove and then re-add the same ts', async () => {
      const dbMessage = createDbMessage('01READD', 'ReAdd', {userId: 1});
      await setupChannelWithPins(handler, CHANNEL_1, dbMessage);

      mockRemovePinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1),
      );
      await handler.remove({channel: CHANNEL_1, ts: '01READD'});
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toEqual([]);

      mockAddPinnedMessage.mockResolvedValue(
        dbMessagesToApiItems(CHANNEL_1, dbMessage),
      );
      await handler.add({
        channel: CHANNEL_1,
        message: createMessage('01READD'),
        duration: 'always',
      });
      expect(handler.getCachedPinnedMessages(CHANNEL_1)).toHaveLength(1);
    });
  });

  // ─── updateMessage ──────────────────────────────────────────────────────

  describe('updateMessage', () => {
    it('should delegate to usePinnedMessages store', async () => {
      const dbMessages = [
        createDbMessage('01A', 'Original', {userId: 1}),
        createDbMessage('01B', 'Other', {userId: 2}),
      ];
      await setupChannelWithPins(handler, CHANNEL_1, ...dbMessages);

      const updatedMessage = {
        ...dbToMappedMessage(dbMessages[0], CHANNEL_1),
        text: 'Updated text',
      };

      handler.updateMessage(CHANNEL_1, updatedMessage as Message);

      const store =
        // eslint-disable-next-line @typescript-eslint/no-var-requires -- access store in test
        require('@modules/chats/store/zustand/usePinnedMessages').usePinnedMessages;
      const pinned = store.getState().pinnedByChannel[CHANNEL_1];
      expect(pinned).toBeDefined();
      const found = pinned?.find((m: Message) => m.ts === '01A');
      expect(found?.text).toBe('Updated text');
    });
  });
});
