import { ulid } from 'ulid';

import api from 'src/config/api';
import { type CursorPagination } from 'src/types/services';
import {
  type CreateMartyVideoCallRequest,
  type CreateVideoCallRequest,
  type GetParticipantsRequest,
  type InviteParticipantsRequest,
  type LiveStream,
  type LiveStreamConfig,
  type MartyVideoCallResponse,
  type Participant,
  type Poll,
  type PollConfiguration,
  type PollFormValues,
  type PollsResponse,
  SourceType,
  type StreamSource,
  type UserTypeStream,
  type VideoCall,
} from 'src/types/stream';

// Livestream services
export const getLivestreamToken = ({
  role,
  callback,
}: {
  role?: UserTypeStream;
  callback: (err: unknown) => void;
}): Promise<string> =>
  api
    .post(`/livestreams/token`, { role })
    .then(res => {
      const token = res?.data?.token;
      if (!token) {
        throw new Error('Livestream token is empty');
      }
      return token;
    })
    .catch(err => {
      callback(err);
      throw err; // Re-throw to prevent undefined return
    });

export const getStream = (userLanguage?: string, source = SourceType.WEB) =>
  api.post<LiveStream>(`/livestreams`, { source, userLanguage });

export const stopLiveStream = (streamId: string) =>
  api.post<LiveStream>(`/livestreams/${streamId}/stop`);

export const updateLiveStreamConfig = (
  streamId: string,
  body: { config: Partial<LiveStreamConfig>; streamSource?: StreamSource },
) => api.patch(`/livestreams/${streamId}`, body);

export const checkThumbnailAvailability = async (thumbnailUrl: string) => {
  try {
    const response = await fetch(thumbnailUrl);
    return response.ok;
  } catch {
    return false;
  }
};

export const createLivestreamPoll = (streamId: string, poll: PollFormValues) =>
  api.post<Poll>(`/livestreams/${streamId}/polls`, {
    poll: {
      title: poll.title,
      pollOptions: poll.pollOptions.map(option => option.value),
      pollConfiguration: {
        viewVotesResult: poll.viewVotesResult,
      },
    },
  });

export const getLivestreamPolls = (streamId: string) =>
  api.get<PollsResponse>(`/livestreams/${streamId}/polls`);

export const updateLivestreamPoll = (
  streamId: string,
  pollId: number,
  poll: PollFormValues,
) =>
  api.patch<Poll>(`/livestreams/${streamId}/polls/${pollId}`, {
    title: poll.title,
    pollOptions: poll.pollOptions.map(option => option.value),
    pollConfiguration: {
      viewVotesResult: poll.viewVotesResult,
    },
  });

export const updateLivestreamPollConfiguration = (
  streamId: string,
  pollId: number,
  pollConfiguration: Partial<PollConfiguration>,
) =>
  api.patch<Poll>(`/livestreams/${streamId}/polls/${pollId}`, {
    pollConfiguration,
  });

export const deleteLivestreamPoll = (streamId: string, pollId: number) =>
  api.delete<Poll>(`/livestreams/${streamId}/polls/${pollId}`);

export const initLivestreamPoll = (streamId: string, pollId: number) =>
  api.post<Poll>(`/livestreams/${streamId}/polls/${pollId}/init`);

export const endLivestreamPoll = (streamId: string, pollId: number) =>
  api.post<Poll>(`/livestreams/${streamId}/polls/${pollId}/end`);

export const showLivestreamPollFinalResults = (
  streamId: string,
  pollId: number,
  show: boolean,
) =>
  api.post<Poll>(`/livestreams/${streamId}/polls/${pollId}/final_results`, {
    show,
  });

// Call services
export const getVideoCallToken = (
  callback: (err: unknown) => void,
): Promise<string> =>
  api
    .post('/calls/token')
    .then(res => {
      const token = res?.data?.token;
      if (!token) {
        throw new Error('Video call token is empty');
      }
      return token;
    })
    .catch(err => {
      callback(err);
      throw err; // Re-throw to prevent undefined return
    });

export const acceptCall = (id: string) => api.post(`/calls/${id}/accept`);

export const rejectCall = (id: string) => api.post(`/calls/${id}/reject`);

export const endCall = (id: string, accessToken: string | null) => {
  // We use fetch instead of axios here because we need to ensure the request is sent
  // even when the page is being unloaded. The `keepalive: true` option in fetch
  // guarantees this behavior, which is not available in axios. This is crucial for
  // properly handling the call rejection when the user closes the tab.
  const baseURL = import.meta.env.VITE_BASE_URL;

  return fetch(`${baseURL}/calls/${id}/end`, {
    method: 'POST',
    keepalive: true,
    headers: {
      'Content-Type': 'application/json',
      'x-humand-origin': 'web',
      Authorization: `Bearer ${accessToken}`,
    },
  });
};

export const missCall = (id: string, participantUserIds: number[]) =>
  api.post(`/calls/${id}/miss`, { participantUserIds });

export const leaveCall = (id: string) => api.post(`/calls/${id}/leave`);

export const ringCall = (id: string) =>
  api.post<VideoCall>(`/calls/${id}/init`);

export const getVideoCall = (id: string) => api.get<VideoCall>(`/calls/${id}`);

export const createVideoCall = ({
  chatId,
  initializationConfig,
}: CreateVideoCallRequest) =>
  api.post<VideoCall>(`/chats/${chatId}/calls`, { initializationConfig });

export const getVideoCallParticipants = ({
  id,
  q,
  limit,
  cursor,
}: GetParticipantsRequest) =>
  api.get<CursorPagination<Participant[]>>(`/calls/${id}/participants`, {
    params: { q, limit, cursor },
  });

export const getInstanceParticipants = ({
  id,
  q,
  limit,
  cursor,
}: GetParticipantsRequest) =>
  api.get<CursorPagination<Participant[]>>(`/calls/${id}/non_participants`, {
    params: { q, limit, cursor },
  });

export const inviteParticipants = ({
  id,
  participantUserIds,
}: InviteParticipantsRequest) =>
  api.post<Participant[]>(`/calls/${id}/invite`, { participantUserIds });

export const createMartyVideoCall = ({
  channelId,
  initializationConfig,
}: CreateMartyVideoCallRequest) =>
  api.post<MartyVideoCallResponse>(
    '/marty/calls.create',
    {
      channel_id: channelId,
      camera_enabled: initializationConfig.cameraEnabled,
    },
    { headers: { 'X-Request-Id': ulid(), 'Idempotency-Key': ulid() } },
  );

export const getActiveVideoCall = (
  params: { channelId: string },
  signal?: AbortSignal,
) =>
  api.get<MartyVideoCallResponse>('/marty/calls.last_active', {
    params,
    headers: { 'X-Request-Id': ulid() },
    signal,
  });
