import { decodeTime, ulid } from 'ulid';
import { type FileUploadOptions } from '@material-hu/hooks/FileAssetUploader/useFileAssetUploader';
import { type FileAsset } from '@material-hu/types/attachments';

import api from 'src/config/api';
import { tokenManager } from 'src/config/tokens';
import {
  type BunnyImageClass,
  RETRY_TIMEOUT_MS,
} from 'src/pages/dashboard/Conversations/constants';
import {
  type AddReactionResponse,
  type ConversationMemberResponse,
  type ConversationsEventsResponse,
  type ConversationsEventsResult,
  type ConversationsInfoResponse,
  type ConversationsMessagesResponse,
  type ConversationsOpenResponse,
  type ConversationsResponse,
  type ConversationsUnreadsResponse,
  type ConversationsUsersInfo,
  type ConversationUsersResponse,
  type EditMessageResponse,
  Format,
  type GetConversationMessagesParams,
  type MessageInfoResponse,
  type ParamsConversationEvents,
  type ParamsForward,
  type ParamsReaction,
  type ParamsSendMessageApi,
  type PinnedMessagesResponse,
  type PropertiesConversation,
  type ReactionsResponse,
  type RemoveReactionResponse,
  type SearchMessagesResponse,
  type SendFileItem,
  type SendImagesType,
  type SendMessageResponseData,
  type SystemMessages,
} from 'src/pages/dashboard/Conversations/types';
import {
  getDimensions,
  getVideoMetadata,
  uploadFileToS3,
  uploadVideoToS3,
} from 'src/services/attachments';
import { FileTypes, mimes } from 'src/types/attachments';
import { bytesToSize } from 'src/utils/bytes';
import { compressFile, getArrayBuffer, getContentType } from 'src/utils/files';
import { isSafari } from 'src/utils/platform';

export const SERVICE_BASE_URL = 'marty';

export const getConversationList = ({
  q,
  limit,
  pinned = false,
  cursor = null,
  signal,
}: {
  q: string;
  limit: number;
  pinned: boolean;
  cursor: string | null;
  signal?: AbortSignal;
}) =>
  api.get<ConversationsResponse>(`${SERVICE_BASE_URL}/conversations.list`, {
    params: { q, limit, cursor, pinned },
    headers: { 'X-Request-Id': ulid() },
    signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const getUsersList = (
  q: string,
  limit: number,
  cursor = null,
  signal?: AbortSignal,
) =>
  api.get<ConversationUsersResponse>(`${SERVICE_BASE_URL}/users.list`, {
    params: { q, limit, cursor },
    headers: { 'X-Request-Id': ulid() },
    signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const getUsersInfo = (user: string) =>
  api.get<ConversationsUsersInfo>(`${SERVICE_BASE_URL}/users.info`, {
    params: { user },
    headers: { 'X-Request-Id': ulid() },
    timeout: RETRY_TIMEOUT_MS,
  });

export const getConversationMembers = async ({
  channel,
  q,
  limit,
  cursor,
  signal,
}: {
  channel: string;
  q: string;
  limit?: number;
  cursor: string | null;
  signal?: AbortSignal;
}) => {
  const userListMembers = await api.get<ConversationMemberResponse>(
    `${SERVICE_BASE_URL}/conversations.memberships`,
    {
      params: { channel, q, limit, cursor, includeDeletedAndDeactivated: true },
      headers: { 'X-Request-Id': ulid() },
      signal,
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return userListMembers;
};

export const openConversation = ({
  users,
  ulid: ulidParam,
  me,
}: {
  users: string[];
  ulid: string;
  me?: string;
}) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.open`,
    { users, me },
    {
      headers: { 'Idempotency-Key': ulidParam, 'X-Request-Id': ulidParam },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const sendMessage = ({
  channel,
  text,
  ulid: ulidParam,
  attachments,
  reply_ts,
}: ParamsSendMessageApi) =>
  api.post<SendMessageResponseData>(
    `${SERVICE_BASE_URL}/chat.postMessage`,
    {
      channel,
      reply_ts,
      text,
      attachments,
      metadata: { event_payload: { id: ulidParam } },
    },
    {
      headers: { 'Idempotency-Key': ulidParam, 'X-Request-Id': ulidParam },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const getConversationsInfo = (args: {
  params: { channel: string };
  headers: { 'X-Request-Id': string; 'X-Call-Site': string };
  signal?: AbortSignal;
}) =>
  api.get<ConversationsInfoResponse>(`${SERVICE_BASE_URL}/conversations.info`, {
    params: args.params,
    headers: args.headers,
    signal: args.signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const getConversationMessages = (args: {
  params: GetConversationMessagesParams;
  signal?: AbortSignal;
}) => {
  return api
    .get<ConversationsMessagesResponse>(
      `${SERVICE_BASE_URL}/conversations.history`,
      {
        params: args.params,
        headers: { 'X-Request-Id': ulid() },
        signal: args.signal,
        timeout: RETRY_TIMEOUT_MS,
      },
    )
    .then(response => {
      response.data?.messages?.sort(
        (a, b) =>
          decodeTime(a.hu_data.message_ts) - decodeTime(b.hu_data.message_ts),
      );
      return response;
    });
};

export const getConversationMessagesPeak = ({
  signal,
  ...params
}: {
  channel: string;
  limit: number;
  pivot: string;
  types: string;
  signal?: AbortSignal;
}) => {
  return api.get<ConversationsMessagesResponse>(
    `${SERVICE_BASE_URL}/conversations.history.peak`,
    {
      params,
      headers: { 'X-Request-Id': ulid() },
      signal,
      timeout: RETRY_TIMEOUT_MS,
    },
  );
};

export const markMessage = (channel: string, ts?: string) =>
  api.post<string>(
    `${SERVICE_BASE_URL}/conversations.mark`,
    {
      channel,
      ts,
    },
    {
      headers: { 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const getUnreadChannels = ({
  cursor,
  signal,
}: {
  cursor?: string;
  signal?: AbortSignal;
}) =>
  api.get<ConversationsUnreadsResponse>(
    `${SERVICE_BASE_URL}/conversations.unreads`,
    {
      params: { cursor },
      headers: { 'X-Request-Id': ulid() },
      signal,
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const createGroup = (name: string, picture: FileAsset) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.create`,
    { name, picture },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const inviteMembers = (channel: string, users: string[]) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.invite`,
    { channel, users },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const editConversations = (
  channel: string,
  name?: string,
  picture?: SendImagesType,
  description?: string,
) =>
  api.patch<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.edit`,
    { channel, name, picture, description },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const getCorrectFileUrl = async ({
  url,
  signal,
  priority = 'low',
  imageClass,
}: {
  url?: string;
  signal?: AbortSignal;
  priority?: RequestPriority;
  imageClass?: BunnyImageClass;
}) => {
  const isSafariBrowser = isSafari();
  let { accessToken } = tokenManager.getCorrectToken();

  if (!tokenManager.isTokenValid()) {
    try {
      accessToken = await tokenManager.getValidAccessToken();
    } catch (error) {
      throw new Error('Failed to refresh access token');
    }
  }
  const params = new URLSearchParams();
  params.append('fs', `${isSafariBrowser}`);
  if (imageClass) {
    params.append('class', imageClass);
  }

  const response = await fetch(`${url}?${params}`, {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'X-Request-Id': ulid(),
    },
    signal,
    priority,
  });

  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`);
  }

  if (isSafariBrowser) {
    const responseJson = await response.json();
    return responseJson.url;
  }

  const blob = await response.blob();
  const blobUrl = URL.createObjectURL(blob);
  return blobUrl;
};

export const leaveGroup = (channel: string, user: string) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.kick`,
    { channel, user },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const grantPermissionsAdmin = (channel: string, user: string) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.admins.grant`,
    { channel, user },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const revokePermissionsAdmin = (channel: string, user: string) =>
  api.post<ConversationsOpenResponse>(
    `${SERVICE_BASE_URL}/conversations.admins.revoke`,
    { channel, user },
    {
      headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const editMessage = async (
  channel: string,
  ts: string,
  text: string,
  attachments: SendFileItem[] | undefined,
  blocks: SystemMessages | undefined,
): Promise<EditMessageResponse> => {
  const response = await api.post<EditMessageResponse>(
    `${SERVICE_BASE_URL}/chat.update`,
    {
      channel,
      text,
      ts,
      attachments,
      blocks,
    },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return response.data;
};

export const addReaction = async ({
  channel,
  ts,
  name,
}: ParamsReaction): Promise<AddReactionResponse> => {
  const response = await api.post<AddReactionResponse>(
    `${SERVICE_BASE_URL}/reactions.add`,
    { channel, ts, name },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return response.data;
};

export const removeReaction = async ({
  channel,
  ts,
  name,
}: ParamsReaction): Promise<RemoveReactionResponse> => {
  const response = await api.post<RemoveReactionResponse>(
    `${SERVICE_BASE_URL}/reactions.remove`,
    { channel, ts, name },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return response.data;
};

export const getReactions = async ({
  channel,
  ts,
  signal,
}: {
  channel: string;
  ts: string;
  signal?: AbortSignal;
}) => {
  const response = await api.get<ReactionsResponse>(
    `${SERVICE_BASE_URL}/reactions.get`,
    {
      params: { channel, ts },
      headers: {
        'X-Request-Id': ulid(),
      },
      signal,
      timeout: RETRY_TIMEOUT_MS,
    },
  );
  return response.data.message.reactions;
};

export const deleteMessage = async ({
  channel,
  ts,
}: {
  channel: string;
  ts: string;
}) => {
  const response = await api.post(
    `${SERVICE_BASE_URL}/chat.delete`,
    { channel, ts },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return response.data;
};

export const forwardMessage = ({
  source_message_ts,
  source_channel_ts,
  channel_destinations,
  user_destinations,
}: ParamsForward) =>
  api.post(
    `${SERVICE_BASE_URL}/chat.forwardMessage`,
    {
      source_message_ts,
      source_channel_ts,
      channel_destinations,
      user_destinations,
    },
    {
      headers: {
        'Idempotency-Key': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const conversationEvents = async ({
  oldest,
  cursor,
  inclusive = false,
  unread = false,
  limit = '200',
  signal,
}: ParamsConversationEvents = {}): Promise<ConversationsEventsResult> => {
  const response = await api.get<ConversationsEventsResponse>(
    `${SERVICE_BASE_URL}/conversations.events`,
    {
      params: {
        oldest,
        inclusive,
        unread,
        limit,
        cursor,
      },
      headers: { 'X-Request-Id': ulid() },
      signal,
      timeout: RETRY_TIMEOUT_MS,
    },
  );

  return {
    status: response.status,
    messages: response.data.messages,
    response_metadata: response.data?.response_metadata,
    config: response.config,
  };
};

export const updateConversationProperties = (
  channel: string,
  properties: PropertiesConversation,
) =>
  api.post(
    `${SERVICE_BASE_URL}/conversations.properties.set`,
    {
      channel,
      properties,
    },
    {
      headers: {
        'Idempotency-Key': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const getMembersReadMessage = ({
  channel,
  ts,
  limit = 100,
  cursor = null,
  signal,
}: {
  channel: string;
  ts: string;
  limit?: number;
  cursor?: string | null;
  signal?: AbortSignal;
}) =>
  api.get<MessageInfoResponse>(`${SERVICE_BASE_URL}/message.details`, {
    params: { channel, ts, limit, cursor },
    headers: { 'X-Request-Id': ulid() },
    signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const searchMessages = ({
  channelId,
  query,
  limit = 99,
  cursor = null,
  signal,
}: {
  channelId: string;
  query: string;
  limit?: number;
  cursor?: string | null;
  signal?: AbortSignal;
}) =>
  api.get<SearchMessagesResponse>(`${SERVICE_BASE_URL}/search.messages`, {
    params: { channelId, query, limit, cursor, sortDir: 'desc' },
    headers: { 'X-Request-Id': ulid() },
    signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const getPinnedMessages = (channel: string, signal?: AbortSignal) =>
  api.get<PinnedMessagesResponse>(`${SERVICE_BASE_URL}/pins.list`, {
    params: { channel },
    headers: { 'X-Request-Id': ulid() },
    signal,
    timeout: RETRY_TIMEOUT_MS,
  });

export const addPin = ({
  channel,
  message_ts,
  expires_at,
}: {
  channel: string;
  message_ts: string;
  expires_at: number | null;
}) =>
  api.post<PinnedMessagesResponse>(
    `${SERVICE_BASE_URL}/pins.add`,
    { channel, message_ts, expires_at },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const removePin = ({
  channel,
  message_ts,
}: {
  channel: string;
  message_ts: string;
}) =>
  api.post<PinnedMessagesResponse>(
    `${SERVICE_BASE_URL}/pins.remove`,
    { channel, message_ts },
    {
      headers: {
        'Idempotency-Key': ulid(),
        'X-Request-Id': ulid(),
      },
      timeout: RETRY_TIMEOUT_MS,
    },
  );

export const uploadConversationFile = async (
  file: File,
  options?: FileUploadOptions,
): Promise<FileAsset> => {
  const signal = options?.signal as AbortSignal | undefined;
  const forceFormat = options?.forceFormat as string;

  const uploadAsImage = forceFormat === Format.IMAGE && file.type !== mimes.SVG;
  const uploadAsVideo = forceFormat === Format.VIDEO;

  if (uploadAsImage) {
    const compressed = await compressFile(file, { mimeType: file.type });
    const dimensions = await getDimensions(compressed);
    const arrayBuffer = await getArrayBuffer(compressed);
    const contentType = compressed.type;

    const { data: fileAsset } = await api.post<FileAsset>(
      '/file-assets',
      {
        name: compressed.name,
        type: FileTypes.IMAGE,
        contentType,
        size: bytesToSize(compressed.size),
        width: dimensions.width,
        height: dimensions.height,
      },
      { signal },
    );

    await uploadFileToS3(
      decodeURI(fileAsset.uploadUrl!),
      arrayBuffer,
      contentType,
      signal,
    );

    return fileAsset;
  }

  if (uploadAsVideo) {
    const metadata = await getVideoMetadata(file);
    const contentType = file.type;

    const { data: fileAsset } = await api.post<FileAsset>(
      '/file-assets',
      {
        name: file.name,
        type: FileTypes.VIDEO,
        contentType,
        size: bytesToSize(file.size),
        width: metadata.width,
        height: metadata.height,
        metadata: { durationInMs: Math.round(metadata.durationInMs) },
      },
      { signal },
    );

    await uploadVideoToS3(
      decodeURI(fileAsset.uploadUrl!),
      contentType,
      signal,
      file,
    );

    return fileAsset;
  }

  const contentType = file.type || getContentType(file.name);
  const arrayBuffer = await getArrayBuffer(file);

  const { data: fileAsset } = await api.post<FileAsset>(
    '/file-assets',
    {
      name: file.name,
      type: FileTypes.FILE,
      contentType,
      size: bytesToSize(file.size),
    },
    { signal },
  );

  await uploadFileToS3(
    decodeURI(fileAsset.uploadUrl!),
    arrayBuffer,
    contentType,
    signal,
  );

  return fileAsset;
};
