import RNFetchBlob from 'react-native-blob-util';
import api, {HEADERS} from '@config/api';
import {AmplitudeConversationTypes} from '@modules/chats/constants';
import {
  Asset,
  ChatEventType,
  Message,
  MessageEvent,
} from '@modules/chats/interfaces';
import {OrchestratorUpdaterService} from '@modules/chats/instances/updaters';
import {getConversationById, getFullConversation} from '@modules/chats/db';
import {
  mapConversationFromServer,
  mapMembershipFromServer,
  mapMessageFromServer,
  mapUserFromServer,
} from '@modules/chats/utils/mappers';
import {
  getMessagesWithLocalData,
  sortMessagesByTsHelper,
  splitAnchorFromMessages,
} from '@modules/chats/utils/messages';
import {getMessageType} from '@modules/chats/utils/amplitude';
import {FileMessagesLogger} from '@modules/chats/instances/FileMessagesLogger';
import {asyncMap, logAmplitudeEvent} from '@shared/utils';
import {AMPLITUDE_EVENTS} from '@shared/constants';

import {
  ConversationMembersParams,
  ConversationMembersResponse,
  ConversationOpenParams,
  ConversationOpenResponse,
  GetConversationHistoryResponse,
  GetConversationHistoryPeakResponse,
  PostMessageParams,
  PostMessageResponse,
  GetConversationInfoResponse,
  GetConversationInfoParams,
  GetEventsParams,
  GetEventsResponse,
  PostConversationsMarkResponse,
  PostConversationsMarkParams,
  GetRecentParams,
  GetRecentResponse,
  ConversationCreateParams,
  ConversationInviteParams,
  ConversationMembershipsResponse,
  ConversationKickParams,
  ConversationKickResponse,
  ConversationGrantAdminParams,
  ConversationGrantAdminResponse,
  GenerateAssetParams,
  GenerateAssetResponse,
  UpdateConversationParams,
  GetUsersUpdatesResponse,
  GetUsersUpdatesParams,
  EditMessageResponse,
  EditMessageParams,
  DeleteMessageResponse,
  DeleteMessageParams,
  ForwardMessageParams,
  ForwardMessageResponse,
  GetConversationsParams,
  GetConversationsResponse,
  PostConversationsMarkReceivedParams,
  SetConversationPropertiesParams,
  SetConversationPropertiesResponse,
  MessageInfoResponse,
  SearchMessagesResponse,
  SearchMessagesRequestParams,
  AddPinnedMessageParams,
  AddPinnedMessageResponse,
  RemovePinnedMessageParams,
  RemovePinnedMessageResponse,
  GetPinnedMessagesParams,
  GetPinnedMessagesResponse,
} from './interfaces';
import {CHATS_BASE_URL} from './constants';

const isEventMessage = (message: MessageEvent) => {
  return [ChatEventType.message, ChatEventType.messageUpdate].includes(
    message.type,
  );
};

export const getConversations = async ({
  limit = 100,
  cursor,
  search,
  unread,
}: GetConversationsParams) => {
  // Get server conversations
  const response = await api.get<GetConversationsResponse>(
    `${CHATS_BASE_URL}/conversations.list`,
    {limit, cursor, q: search, unread},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch conversations');
  }

  const conversations = await asyncMap(
    response.data.channels,
    async conversation => await mapConversationFromServer(conversation),
  );

  return {
    data: conversations,
    nextCursor: response.data.response_metadata?.next_cursor || null,
  };
};

export const getConversationInfo = async (
  params: GetConversationInfoParams,
) => {
  if (!params.fromServer) {
    const localConversation = await getFullConversation(params.channel);

    if (localConversation) {
      return [
        localConversation,
        {isFromDB: true, isFromServer: false},
      ] as const;
    }
  }

  const response = await api.get<GetConversationInfoResponse>(
    `${CHATS_BASE_URL}/conversations.info`,
    params,
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch conversations');
  }

  return [
    await mapConversationFromServer(response.data.channel),
    {isFromDB: false, isFromServer: true},
  ] as const;
};

export const getConversationMembers = async (
  params: ConversationMembersParams,
) => {
  const response = await api.get<ConversationMembersResponse>(
    `${CHATS_BASE_URL}/conversations.members`,
    params,
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch conversation members');
  }

  return {
    data: response.data.members,
    nextCursor: response.data.response_metadata?.next_cursor,
  };
};

export const getConversationMemberships = async (
  params: ConversationMembersParams,
) => {
  const response = await api.get<ConversationMembershipsResponse>(
    `${CHATS_BASE_URL}/conversations.memberships`,
    params,
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch conversation members');
  }

  // Remove deleted memberships
  const memberships = response.data.memberships.filter(
    item => !item.deleted_at,
  );

  return {
    data: memberships.map(mapMembershipFromServer),
    nextCursor: response.data.response_metadata?.next_cursor,
  };
};

export const openConversation = async (
  {users, ...params}: ConversationOpenParams,
  idempotencyKey: string,
) => {
  const response = await api.post<ConversationOpenResponse>(
    `${CHATS_BASE_URL}/conversations.open`,
    {users: users.map(user => user.id), return_im: true, ...params},
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to open conversations');
  }

  return await mapConversationFromServer(response.data.channel);
};

export const createConversation = async (
  params: Pick<ConversationCreateParams, 'name'>,
  idempotencyKey: string,
) => {
  const response = await api.post<ConversationOpenResponse>(
    `${CHATS_BASE_URL}/conversations.create`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to create conversation');
  }

  return await mapConversationFromServer(response.data.channel);
};

export const kickUserFromConversation = async (
  params: ConversationKickParams & {idempotencyKey: string},
) => {
  const response = await api.post<ConversationKickResponse>(
    `${CHATS_BASE_URL}/conversations.kick`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(
      response.problem || 'Failed to invite users to conversation',
    );
  }

  return response.data;
};

export const revokeAdminPermissionInConversation = async (
  params: ConversationGrantAdminParams & {idempotencyKey: string},
) => {
  const response = await api.post<ConversationGrantAdminResponse>(
    `${CHATS_BASE_URL}/conversations.admins.revoke`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to revoke admin permission');
  }

  return response.data;
};

export const grantAdminPermissionInConversation = async (
  params: ConversationGrantAdminParams & {idempotencyKey: string},
) => {
  const response = await api.post<ConversationGrantAdminResponse>(
    `${CHATS_BASE_URL}/conversations.admins.grant`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to grant admin permission');
  }

  return response.data;
};

export const inviteUsersToConversation = async (
  params: ConversationInviteParams & {idempotencyKey: string},
) => {
  const response = await api.post<ConversationOpenResponse>(
    `${CHATS_BASE_URL}/conversations.invite`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(
      response.problem || 'Failed to invite users to conversation',
    );
  }

  return response.data;
};

const logMessageSent = async (
  channel: string,
  attachments: Asset[],
  text: string,
) => {
  let conversationType = null;
  try {
    const conversation = await getConversationById(channel);
    conversationType = conversation?.is_channel
      ? AmplitudeConversationTypes.Group
      : AmplitudeConversationTypes.Individual;
  } catch (error) {
    /* do nothing */
  }

  logAmplitudeEvent(AMPLITUDE_EVENTS.CHATS_2_MESSAGE_SENT, {
    conversationID: channel,
    conversationType,
    messageType: getMessageType(text, attachments),
  });
};

export const postMessage = async ({
  channel,
  idempotencyKey,
  text,
  messageId,
  attachments,
  reply_ts,
  generated_by,
}: PostMessageParams) => {
  const payload = {
    channel,
    text,
    attachments,
    reply_ts,
    generated_by,
    ...(messageId ? {metadata: {event_payload: {id: messageId}}} : {}),
  };

  const response = await api.post<PostMessageResponse>(
    `${CHATS_BASE_URL}/chat.postMessage`,
    payload,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed post message');
  }

  logMessageSent(channel, attachments || [], text);

  return {
    data: await mapMessageFromServer(response.data.message),
  };
};

export const forwardMessage = async ({
  text,
  channel,
  attachments,
  messageId,
  destinationChannelIds,
  destinationUserIds,
  idempotencyKey,
}: ForwardMessageParams) => {
  const payload = {
    source_message_ts: messageId,
    source_channel_ts: channel,
    channel_destinations: destinationChannelIds,
    user_destinations: destinationUserIds,
  };

  const response = await api.post<ForwardMessageResponse>(
    `${CHATS_BASE_URL}/chat.forwardMessage`,
    payload,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed forward message');
  }

  const data = response.data.forwarded_messages.map(msg => {
    return mapMessageFromServer(msg);
  });

  logMessageSent(channel, attachments || [], text);

  return {
    data: await Promise.all(data),
  };
};

export const getEvents = async (params: GetEventsParams) => {
  const response = await api.get<GetEventsResponse>(
    `${CHATS_BASE_URL}/events`,
    params,
    {timeout: 60_000}, // 60 seconds
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch events');
  }

  return {
    data: response.data.messages,
    nextCursor: response.data.response_metadata?.next_cursor,
    requestId: response.headers?.[HEADERS.REQUEST_ID],
  };
};

export const getRecent = async (params: GetRecentParams) => {
  const response = await api.get<GetRecentResponse>(
    `${CHATS_BASE_URL}/conversations.recent`,
    params,
  );

  if (!response.ok || response?.data == null) {
    throw new Error(response.problem || 'Failed to fetch recent');
  }

  const conversations = await asyncMap(
    response.data.conversations,
    async conversation => ({
      group_conversation: await mapConversationFromServer(
        conversation.group_conversation,
      ),
      latest_messages: await asyncMap(
        conversation.latest_messages,
        async message => await mapMessageFromServer(message),
      ),
    }),
  );

  return {
    data: conversations,
    nextCursor: response.data.response_metadata?.next_cursor,
  };
};

export const getMeUnreads = async () => {
  const {data} = await api.get<number>(`${CHATS_BASE_URL}/me.unreads`);
  return data || 0;
};

export const postConversationsMark = async ({
  channel,
  idempotencyKey,
  ts,
}: PostConversationsMarkParams) => {
  const response = await api.post<PostConversationsMarkResponse>(
    `${CHATS_BASE_URL}/conversations.mark`,
    {channel, ts},
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to mark conversation as read');
  }
};

export const postConversationMarkReceived = async ({
  channel,
  last_received_message_ts,
  idempotencyKey,
}: PostConversationsMarkReceivedParams) => {
  const response = await api.post<PostConversationsMarkResponse>(
    `${CHATS_BASE_URL}/conversations.markReceived`,
    {channel, message_ts: last_received_message_ts},
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: idempotencyKey}},
  );
  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to mark conversation as read');
  }
};

export interface FetchMessagesResult {
  messages: Message[];
  hasMore: boolean;
  // On inclusive fetches, the message at the anchor ts is returned separately
  // so the caller can reconcile a possibly-stale local copy without mixing it
  // into the page.
  anchor?: Message;
}

export interface FetchMessagesAroundResult {
  messages: Message[];
  // Peak returns both directions in one call, so we expose them separately:
  // the caller needs them to set `isStart` / `isEnd` independently on the
  // resulting range. A single `hasMore` doesn't make sense here — it would
  // trigger spurious fetches whenever the window touches just one edge.
  hasMoreAbove: boolean;
  hasMoreBelow: boolean;
}

export const fetchMessagesAbove = async ({
  groupId,
  firstEventTs,
  limit,
  timeout,
}: {
  groupId: string;
  firstEventTs: string;
  limit: number;
  timeout?: number;
}): Promise<FetchMessagesResult> => {
  // Always inclusive: bump the limit by 1 so the caller still gets `limit`
  // messages strictly above the anchor. The anchor itself is returned
  // separately on `result.anchor`.
  const response = await api.get<GetConversationHistoryResponse>(
    `${CHATS_BASE_URL}/conversations.history`,
    {
      channel: groupId,
      latest: firstEventTs,
      limit: limit + 1,
      direction: 'DESC',
      types: 'message',
      inclusive: true,
    },
    timeout ? {timeout} : undefined,
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch messages above');
  }

  // Map, filter and save messages
  const mappedMessages: Message[] = [];

  await asyncMap(response.data.messages, async message => {
    if (isEventMessage(message)) {
      FileMessagesLogger.logMessageTs(
        message.hu_data?.message_ts || message.ts,
      );
      const mappedMessage = await mapMessageFromServer(message);
      const messageWithHeight = await getMessagesWithLocalData([mappedMessage]);
      mappedMessages.push(messageWithHeight[0]);
    }
  });

  // Sort by ts ascending
  mappedMessages.sort(sortMessagesByTsHelper);

  // Split the anchor out so consumers get only strictly-above messages.
  const {messages, anchor} = splitAnchorFromMessages(
    mappedMessages,
    firstEventTs,
  );

  // Skip store update - the caller (useGroups) will handle adding messages to the window
  await OrchestratorUpdaterService.saveNewMessages(
    anchor ? [...messages, anchor] : messages,
    {skipStoreUpdate: true},
  );

  const hasMore = response.data.response_metadata?.next_cursor != null;

  return {messages, hasMore, anchor};
};

export const fetchMessagesBelow = async ({
  groupId,
  firstEventTs,
  limit,
}: {
  groupId: string;
  firstEventTs: string;
  limit: number;
}): Promise<FetchMessagesResult> => {
  // Same inclusive strategy as fetchMessagesAbove: request inclusively
  // (anchor + `limit` newer messages) and split the anchor out on
  // `result.anchor` so the caller can reconcile a stale copy without
  // polluting the page.
  const response = await api.get<GetConversationHistoryResponse>(
    `${CHATS_BASE_URL}/conversations.history`,
    {
      channel: groupId,
      // From date
      oldest: firstEventTs,
      limit: limit + 1,
      direction: 'ASC',
      types: 'message',
      inclusive: true,
    },
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch messages below');
  }

  // Map, filter and save messages
  const mappedMessages: Message[] = [];

  await asyncMap(response.data.messages, async message => {
    if (isEventMessage(message)) {
      FileMessagesLogger.logMessageTs(
        message.hu_data?.message_ts || message.ts,
      );

      const mappedMessage = await mapMessageFromServer(message);
      const messageWithHeight = await getMessagesWithLocalData([mappedMessage]);
      mappedMessages.push(messageWithHeight[0]);
    }
  });

  // Sort by ts ascending
  mappedMessages.sort(sortMessagesByTsHelper);

  // Split the anchor out so consumers get only strictly-below messages.
  const {messages, anchor} = splitAnchorFromMessages(
    mappedMessages,
    firstEventTs,
  );

  // Skip store update - the caller (useGroups) will handle adding messages to the window
  // Anchor has the smallest ts here (below = newer than anchor), so it goes first.
  await OrchestratorUpdaterService.saveNewMessages(
    anchor ? [anchor, ...messages] : messages,
    {skipStoreUpdate: true},
  );

  const hasMore = response.data.response_metadata?.next_cursor != null;

  return {messages, hasMore, anchor};
};

export const fetchMessagesAround = async ({
  groupId,
  targetTs,
  limit = 25,
}: {
  groupId: string;
  targetTs: string;
  limit?: number;
}): Promise<FetchMessagesAroundResult> => {
  // `conversations.history.peak` returns a windowed result (N before + pivot
  // + N after) in a single call, where `limit` is the count per side, plus
  // `next_cursor` / `prev_cursor` in response_metadata for paging past the
  // window.
  const response = await api.get<GetConversationHistoryPeakResponse>(
    `${CHATS_BASE_URL}/conversations.history.peak`,
    {
      channel: groupId,
      pivot: targetTs,
      limit,
      types: 'message',
    },
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(
      response.problem || 'Failed to fetch messages around target',
    );
  }

  const mappedMessages: Message[] = [];

  await asyncMap(response.data.messages, async message => {
    if (isEventMessage(message)) {
      const mappedMessage = await mapMessageFromServer(message);
      const messageWithHeight = await getMessagesWithLocalData([mappedMessage]);
      mappedMessages.push(messageWithHeight[0]);
    }
  });

  const messages = mappedMessages.sort(sortMessagesByTsHelper);

  // Skip store update - the caller (useGroups) will handle adding messages to the window
  await OrchestratorUpdaterService.saveNewMessages(messages, {
    skipStoreUpdate: true,
  });

  const hasMoreAbove = response.data.response_metadata?.prev_cursor != null;
  const hasMoreBelow = response.data.response_metadata?.next_cursor != null;

  return {
    messages,
    hasMoreAbove,
    hasMoreBelow,
  };
};

export const fetchMessage = async (
  chatId: string,
  ts: string,
): Promise<Message> => {
  const response = await api.get<GetConversationHistoryResponse>(
    `${CHATS_BASE_URL}/conversations.history`,
    {
      channel: chatId,
      oldest: ts,
      limit: 1,
      direction: 'ASC',
      types: 'message',
      inclusive: true,
    },
  );

  if (!response.ok || !response.data?.ok || !response.data.messages.length) {
    throw new Error(response.problem || 'Failed to fetch message');
  }

  return mapMessageFromServer(response.data.messages[0]);
};

export const updateConversation = async (params: UpdateConversationParams) => {
  const response = await api.patch<ConversationOpenResponse>(
    `${CHATS_BASE_URL}/conversations.edit`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to update conversation');
  }

  return await mapConversationFromServer(response.data.channel);
};

export const generateAsset = async (params: GenerateAssetParams) => {
  if (params.metadata) {
    const durationInMs = params.metadata.durationInMs;
    params.metadata.durationInMs = Math.trunc(durationInMs ?? 0);
  }
  const response = await api.post<GenerateAssetResponse>(
    '/file-assets',
    params,
  );
  if (!response.ok || !response.data) {
    throw new Error(response.problem || 'Failed to generate Asset');
  }

  return response.data;
};

export const uploadMedia = async (
  filePath: string,
  uploadUrl: string,
  contentType: string,
) => {
  const response = await RNFetchBlob.fetch(
    'PUT',
    uploadUrl,
    {
      'Content-Type': contentType,
    },
    RNFetchBlob.wrap(filePath),
  );

  const responseInfo = response.info();

  if (responseInfo.status !== 200) {
    throw new Error('Failed to upload media');
  }

  return response;
};

export const getUsersUpdates = async (params: GetUsersUpdatesParams) => {
  const response = await api.get<GetUsersUpdatesResponse>(
    `${CHATS_BASE_URL}/users.updated`,
    params,
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to fetch users updates');
  }

  return {
    data: response.data.members.map(mapUserFromServer),
    nextCursor: response.data.response_metadata?.next_cursor,
  };
};

export const editMessage = async (params: EditMessageParams) => {
  const response = await api.post<EditMessageResponse>(
    `${CHATS_BASE_URL}/chat.update`,
    {
      channel: params.channel,
      ts: params.ts,
      text: params.text,
    },
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to edit message');
  }

  return await mapMessageFromServer(response.data.message);
};

export const deleteMessage = async (params: DeleteMessageParams) => {
  const response = await api.post<DeleteMessageResponse>(
    `${CHATS_BASE_URL}/chat.delete`,
    params,
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to delete message');
  }

  return response.data;
};

export const setConversationProperties = async (
  params: SetConversationPropertiesParams,
) => {
  const response = await api.post<SetConversationPropertiesResponse>(
    `${CHATS_BASE_URL}/conversations.properties.set`,
    {
      channel: params.channel,
      properties: params.properties,
    },
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(
      response.problem || 'Failed to set conversation properties',
    );
  }

  return await mapConversationFromServer(response.data.channel);
};

export const getMessageInfo = async (params: {
  ts: string;
  channel: string;
  limit?: number;
  cursor?: string;
}) => {
  const response = await api.get<MessageInfoResponse>(
    `${CHATS_BASE_URL}/message.details`,
    {
      ...params,
      limit: params.limit || 100,
    },
  );

  if (!response.ok) {
    throw new Error(response.problem || 'Failed to get message info');
  }

  return response.data;
};

export const searchMessages = async (params: SearchMessagesRequestParams) => {
  const {signal} = params;

  const response = await api.get<SearchMessagesResponse>(
    `${CHATS_BASE_URL}/search.messages`,
    {...params, limit: params.limit || 100, sortDir: params.sortDir || 'desc'},
    {signal}, // needed to abort the request on a new search when the prev is still loading
  );

  if (!response.ok) {
    throw new Error(response.problem || 'Failed to search messages');
  }

  return {
    data: response.data?.matches || [],
    nextCursor: response.data?.response_metadata?.next_cursor ?? null,
  };
};

export class PinRaceConditionError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'PinRaceConditionError';
  }
}

const PIN_DUPLICATED_ACTION_ERRORS = ['already_pinned', 'no_pin'];

export const addPinnedMessage = async (
  params: AddPinnedMessageParams,
): Promise<AddPinnedMessageResponse> => {
  const response = await api.post<AddPinnedMessageResponse>(
    `${CHATS_BASE_URL}/pins.add`,
    {
      channel: params.channel,
      message_ts: params.ts,
      expires_at: params.expiresAt,
    },
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    if (PIN_DUPLICATED_ACTION_ERRORS.includes(response.data?.error ?? '')) {
      throw new PinRaceConditionError(response.data?.error ?? '');
    }
    throw new Error(response.problem || 'Failed to add pinned message');
  }

  return response.data;
};

export const removePinnedMessage = async (
  params: RemovePinnedMessageParams,
): Promise<RemovePinnedMessageResponse> => {
  const response = await api.post<RemovePinnedMessageResponse>(
    `${CHATS_BASE_URL}/pins.remove`,
    {channel: params.channel, message_ts: params.ts},
    {headers: {[HEADERS.IDEMPOTENCY_KEY]: params.idempotencyKey}},
  );

  if (!response.ok || !response.data?.ok) {
    if (PIN_DUPLICATED_ACTION_ERRORS.includes(response.data?.error ?? '')) {
      throw new PinRaceConditionError(response.data?.error ?? '');
    }
    throw new Error(response.problem || 'Failed to remove pinned message');
  }

  return response.data;
};

export const getPinnedMessages = async (
  params: GetPinnedMessagesParams,
): Promise<GetPinnedMessagesResponse> => {
  const response = await api.get<GetPinnedMessagesResponse>(
    `${CHATS_BASE_URL}/pins.list`,
    {channel: params.channel},
  );

  if (!response.ok || !response.data?.ok) {
    throw new Error(response.problem || 'Failed to get pinned messages');
  }

  return response.data;
};

export * from './interfaces';
export * from './constants';
export * from './usersServices';
export * from './reactionsServices';
export * from './logsServices';
