'use client';

import { ID3Writer } from 'browser-id3-writer';

import { toast } from '@/components/toast/Toast';
import { ApiClient } from '@/lib/apiClient';
import type { components } from '@/lib/gen';
import { Clip, Profile } from '@/state/clipStore';
import { PlanFeature, SessionStore } from '@/state/sessionStore';
import { getClipTitle } from '@/utils/clip';

import { isMobileBrowser } from './device';
import downloadAudioFilesAsZip from './downloadAudioFilesAsZip';
import { isFeatureEnabledForPlan } from './session';
import { getShareLink } from './share';
import { sleep } from './utils';

/**
 * Determines if downloads should be disabled for a clip.
 * Returns true if downloads should be disabled, false if they should be allowed.
 *
 * @param clip - The clip to check
 * @param session - The session store containing user info and feature flags
 * @returns true if downloads are disabled, false if allowed
 */
export function isDownloadDisabled(clip: Clip, session: SessionStore): boolean {
  const isOwner = clip.user_id === session?.userId;

  // If the user owns the clip, they can always download it
  if (isOwner) {
    return false;
  }

  // For non-owners, check if downloads are disabled due to remix contest
  return !!(
    clip.download_disabled_reason &&
    session?.flags?.['remix-contest-disable-downloads']
  );
}

async function incrementPlayCount(
  apiClient: ApiClient,
  clipId: string,
  action: string
) {
  apiClient.POST('/api/gen/{gen_id}/increment_action_count/', {
    params: { path: { gen_id: clipId } },
    body: {
      action: action,
    },
  });
}

export async function logDownloadToBilling(
  apiClient: ApiClient,
  clipId: string
) {
  try {
    await apiClient.POST('/api/billing/clips/{clip_id}/download/', {
      params: { path: { clip_id: clipId } },
    });
  } catch (error) {
    console.warn('Failed to log download to billing:', error);
  }
}

async function incrementPlayCounts(
  apiClient: ApiClient,
  clipIds: string[],
  action: string
) {
  apiClient.POST('/api/gen/increment_action_counts/', {
    body: {
      action: action,
      gen_ids: clipIds,
    },
  });
}

export async function getMediaBlob(
  clip: Clip,
  mediaType: 'audio' | 'video' | 'audio-wav',
  downloadUrl?: string
) {
  const url = !!downloadUrl
    ? downloadUrl
    : mediaType === 'audio'
      ? clip.audio_url
      : clip.video_url;

  if (!url) {
    console.error(`No ${mediaType} URL available.`);
    return;
  }

  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Network response was not ok: ${response.status}`);
  }

  let arrayBuffer = await response.arrayBuffer();

  // Only add metadata for MP3 files
  if (mediaType === 'audio') {
    const writer = new ID3Writer(arrayBuffer);

    // Fetch the cover art if available
    let coverBuffer: ArrayBuffer | null = null;
    if (clip.image_url) {
      try {
        const coverResponse = await fetch(clip.image_url);
        if (coverResponse.ok) {
          coverBuffer = await coverResponse.arrayBuffer();
        }
      } catch (error) {
        console.warn('Failed to fetch cover art:', error);
      }
    }

    // Set the text metadata tags. see https://id3.org/id3v2.3.0 for reference
    writer
      .setFrame('TIT2', getClipTitle(clip))
      .setFrame('WOAS', getClipUrl(clip));

    if (clip.handle) {
      writer.setFrame('TPE1', [clip.handle]);
    }

    if (clip.caption) {
      writer.setFrame('COMM', {
        description: '',
        text: clip.caption,
      });
    }

    // Add cover art if available
    if (coverBuffer) {
      writer.setFrame('APIC', {
        type: 0x03, // Cover (front)
        data: coverBuffer,
        description: 'Cover',
      });
    }

    // write all tags to the buffer we're going to download
    arrayBuffer = writer.addTag();
  }

  return new Blob([arrayBuffer], {
    type:
      mediaType === 'audio'
        ? 'audio/mpeg'
        : mediaType === 'audio-wav'
          ? 'audio/wav'
          : 'video/mp4',
  });
}

export async function logAndGetMediaBlob(
  apiClient: ApiClient,
  clip: Clip,
  mediaType: 'audio' | 'video' | 'audio-wav',
  downloadUrl?: string
) {
  const actionType =
    mediaType === 'audio'
      ? 'download_audio'
      : mediaType === 'audio-wav'
        ? 'download_audio_wav'
        : 'download_video';
  incrementPlayCount(apiClient, clip.id, actionType);
  return getMediaBlob(clip, mediaType, downloadUrl);
}

export async function logAndGetMediaBlobs(
  apiClient: ApiClient,
  clipsAndDownloadUrls: { clip: Clip; downloadUrl?: string }[],
  mediaType: 'audio' | 'video' | 'audio-wav'
) {
  const actionType =
    mediaType === 'audio'
      ? 'download_audio'
      : mediaType === 'audio-wav'
        ? 'download_audio_wav'
        : 'download_video';

  incrementPlayCounts(
    apiClient,
    clipsAndDownloadUrls.map(({ clip }) => clip.id),
    actionType
  );

  return Promise.all(
    clipsAndDownloadUrls.map(({ clip, downloadUrl }) =>
      getMediaBlob(clip, mediaType, downloadUrl)
    )
  );
}

export async function downloadMedia(
  apiClient: ApiClient,
  clip: Clip,
  mediaType: 'audio' | 'video' | 'audio-wav',
  session: SessionStore,
  downloadUrl?: string,
  videoRegenerated: boolean = false
): Promise<void> {
  const url = !!downloadUrl
    ? downloadUrl
    : mediaType === 'audio'
      ? clip.audio_url
      : clip.video_url;

  const fileExtension =
    mediaType === 'audio' ? 'mp3' : mediaType === 'audio-wav' ? 'wav' : 'mp4';

  const title = `${getClipTitle(clip)}.${fileExtension}`;

  try {
    const blob = await logAndGetMediaBlob(
      apiClient,
      clip,
      mediaType,
      url || undefined
    );

    if (!blob) {
      throw new Error('Could not create media file');
    }

    const blobURL = window.URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = blobURL;
    a.download = title;
    document.body.appendChild(a);
    a.style.display = 'none';
    a.click();
    a.remove();

    // if want to show toast for other media types, just remove this if statement (may want to update the text shown)
    if (mediaType === 'video' && !videoRegenerated) {
      toast({
        title: `Full ${mediaType} downloaded`,
        description: !isFeatureEnabledForPlan(
          session,
          PlanFeature.GenerateSongVideo
        )
          ? `Upgrade subscription to regenerate videos`
          : undefined,
        status: 'success',
        duration: 3000,
        isClosable: true,
      });
    }

    setTimeout(() => {
      window.URL.revokeObjectURL(blobURL);
    }, 100);
  } catch (error) {
    // As a fallback, open the file as a new tab
    // Might apply to Mobile Safari which doesn't support the `download` attribute
    if (typeof window !== 'undefined') {
      window.open(downloadUrl, '_blank');
    }
    console.error('Download failed:', error);
    alert('Download failed, please try again.');
  }
}

export async function downloadClipAudio(
  apiClient: ApiClient,
  clip: Clip,
  session: SessionStore,
  format: 'audio' | 'audio-wav' = 'audio',
  downloadUrl?: string
): Promise<void> {
  await downloadMedia(apiClient, clip, format, session, downloadUrl);
}

export function initDownloadClipWav(apiClient: ApiClient, clipId: string): any {
  apiClient.POST('/api/gen/{clip_id}/convert_wav/', {
    params: { path: { clip_id: clipId } },
  });
}

const opusPromises: Record<string, Promise<string>> = {};

export async function getOpusFileURL(apiClient: ApiClient, clipId: string) {
  const response = await apiClient.GET('/api/gen/{clip_id}/opus_file/', {
    params: {
      path: {
        clip_id: clipId,
      },
    },
  });
  return response.data?.opus_file_url || null;
}

export async function generateOpus(apiClient: ApiClient, clipId: string) {
  const { response, error } = await apiClient.POST(
    '/api/gen/{clip_id}/convert_opus',
    {
      params: { path: { clip_id: clipId } },
    }
  );

  if (!response.ok) {
    throw new Error((error as any)?.message || 'Failed to generate opus file');
  }

  const pollRateMS = 5000;
  let timeout = 24; // 24 * 5 = 120 seconds = 2 mins = should never take this long
  while (timeout-- > 0) {
    const pollResponse = await getOpusFileURL(apiClient, clipId);
    if (pollResponse) {
      return pollResponse;
    }
    await sleep(pollRateMS);
  }
  throw new Error('Opus file generation timed out.');
}

export async function getOrGenerateOpusFileUrl(
  apiClient: ApiClient,
  clipId: string
) {
  if (!opusPromises[clipId]) {
    opusPromises[clipId] = (async () => {
      const currentFileUrl = await getOpusFileURL(apiClient, clipId);
      if (currentFileUrl) return currentFileUrl;

      return await generateOpus(apiClient, clipId);
    })();
  }

  return await opusPromises[clipId]!;
}

const wavPromises: Record<string, Promise<string>> = {};
export async function getOrGenerateWavFileUrl(
  apiClient: ApiClient,
  clipId: string
) {
  if (!wavPromises[clipId]) {
    wavPromises[clipId] = (async () => {
      const downloadWavResponse = await downloadClipWav(apiClient, clipId);
      if (downloadWavResponse.data?.wav_file_url)
        return downloadWavResponse.data?.wav_file_url;

      await initDownloadClipWav(apiClient, clipId);

      let tries = 0;

      // 2 minutes
      while (tries++ < 24) {
        await sleep(5000);
        const downloadWavResponse = await downloadClipWav(apiClient, clipId);
        if (downloadWavResponse.data?.wav_file_url) {
          return downloadWavResponse.data?.wav_file_url;
        }
      }

      throw new Error(`Timed out waiting for ${clipId} wav file to generate`);
    })();
  }

  return await wavPromises[clipId]!;
}

export async function downloadClipWav(apiClient: ApiClient, clipId: string) {
  return await apiClient.GET('/api/gen/{clip_id}/wav_file/', {
    params: { path: { clip_id: clipId } },
  });
}

export async function downloadSamplePack(
  apiClient: ApiClient,
  clip: Clip
): Promise<void> {
  try {
    const response = await apiClient.POST(
      '/api/generate/{clip_id}/generate_sample_pack',
      {
        params: { path: { clip_id: clip.id } },
      }
    );

    if (response.error) {
      throw new Error('Failed to generate sample pack');
    }

    const wavFiles = response.data;

    if (!wavFiles || wavFiles.length === 0) {
      throw new Error('No high-quality stems found');
    }
    await downloadAudioFilesAsZip(
      wavFiles,
      `${getClipTitle(clip)}_sample_pack`
    );
  } catch (error) {
    throw error;
  }
}

export async function downloadClipVideo(
  apiClient: ApiClient,
  clip: Clip,
  session: SessionStore
): Promise<void> {
  downloadMedia(apiClient, clip, 'video', session);
}

const getClipUrl = (clip: Clip) => {
  return `${window.location.origin}/song/${clip.id}`;
};

const getProfileUrl = (handle: string) => {
  return `${window.location.origin}/@${handle}`;
};

const getPersonaUrl = (personaId: string) => {
  return `${window.location.origin}/persona/${personaId}`;
};

export const trackReusePrompt = (apiClient: ApiClient, clip: Clip) => {
  incrementPlayCount(apiClient, clip.id, 'reuse_prompt');
};

export const shareClip = async (
  apiClient: ApiClient,
  clip: Clip,
  time?: number
) => {
  let shareLink =
    (await getShareLink({
      apiClient,
      contentType: 'song',
      contentId: clip.id,
    })) || getClipUrl(clip);
  if (time) {
    const url = new URL(shareLink);
    url.searchParams.set('time', time.toString());
    shareLink = url.toString();
  }
  const shareData: ShareData = {
    url: shareLink,
    title: `Listen to ${getClipTitle(clip)} on Suno! 🎵`,
  };
  incrementPlayCount(apiClient, clip.id, 'share');

  if (
    isMobileBrowser() &&
    window.navigator.canShare &&
    navigator.canShare(shareData)
  ) {
    try {
      navigator.share(shareData);
    } catch (e) {
      console.error('Error sharing:', e);
    }
    return;
  }

  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard
      .writeText(shareLink)
      .then(() => {
        toast({
          title: 'Copied song link to clipboard',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      })
      .catch((err) => {
        console.error('Failed to copy: ', err);
      });
  } else {
    alert('Clipboard not supported. Please copy the URL from the address bar.');
  }
};

export const sharePlaylist = (playlistId: string) => {
  const url = `${window.location.origin}/playlist/${playlistId}`;

  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard
      .writeText(url)
      .then(() => {
        toast({
          title: 'Copied playlist link to clipboard',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      })
      .catch((err) => {
        console.error('Failed to copy: ', err);
        alert(
          'Clipboard not supported. Please manually copy the URL from the address bar.'
        );
      });
  } else {
    alert(
      'Clipboard not supported. Please manually copy the URL from the address bar.'
    );
  }
};

export const shareProfile = (profile: Profile) => {
  const shareData: ShareData = {
    url: getProfileUrl(profile.handle),
    title: `Listen to ${profile.display_name || profile.handle} on Suno! 🎵`,
  };

  if (
    isMobileBrowser() &&
    window.navigator.canShare &&
    navigator.canShare(shareData)
  ) {
    try {
      navigator.share(shareData);
    } catch (e) {
      console.error('Error sharing:', e);
    }
    return;
  }

  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard
      .writeText(getProfileUrl(profile.handle))
      .then(() => {
        toast({
          title: 'Copied profile link to clipboard',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      })
      .catch((err) => {
        console.error('Failed to copy: ', err);
      });
  } else {
    alert('Clipboard not supported. Please copy the URL from the address bar.');
  }
};

export const shareRadio = (songId: string) => {
  const url = `${window.location.origin}/radio/song/${songId}`;

  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard
      .writeText(url)
      .then(() => {
        toast({
          title: 'Copied radio link to clipboard',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      })
      .catch((err) => {
        console.error('Failed to copy: ', err);
        alert(
          'Clipboard not supported. Please manually copy the URL from the address bar.'
        );
      });
  } else {
    alert(
      'Clipboard not supported. Please manually copy the URL from the address bar.'
    );
  }
};

export const sharePersona = (persona: { id: string; name: string }) => {
  const shareData: ShareData = {
    url: getPersonaUrl(persona.id),
    title: `Check out the persona ${persona.name} on Suno! 🎵`,
  };

  if (
    isMobileBrowser() &&
    window.navigator.canShare &&
    navigator.canShare(shareData)
  ) {
    try {
      navigator.share(shareData);
    } catch (e) {
      console.error('Error sharing:', e);
    }
    return;
  }

  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard
      .writeText(getPersonaUrl(persona.id))
      .then(() => {
        toast({
          title: 'Copied Persona link to clipboard',
          status: 'info',
          duration: 2000,
          isClosable: true,
        });
      })
      .catch((err) => {
        console.error('Failed to copy: ', err);
      });
  } else {
    alert('Clipboard not supported. Please copy the URL from the address bar.');
  }
};

export function createShareAsset(
  apiClient: ApiClient,
  clip: Clip,
  config: {
    asset_config: Record<string, string | number | boolean>;
    clip_start_time: number;
    clip_end_time: number;
  }
) {
  return apiClient.POST('/api/gen/{gen_id}/share_asset', {
    params: { path: { gen_id: clip.id } },
    body: config,
  });
}

export function getShareAssetStatus(
  apiClient: ApiClient,
  clip: Clip,
  assetId: string
) {
  return apiClient.GET('/api/gen/{gen_id}/share_asset/{asset_id}', {
    params: {
      path: {
        gen_id: clip.id,
        asset_id: assetId,
      },
    },
  });
}

export async function downloadMultitrackV2(
  apiClient: ApiClient,
  renderStateBody: components['schemas']['RenderStateMultitrackSpec']
): Promise<void> {
  try {
    const result = await apiClient.POST('/api/studio/render-state-multitrack', {
      body: renderStateBody,
    });

    if (!result.data?.download_url) {
      throw new Error('No download URL returned from server');
    }

    const downloadUrl = result.data.download_url;

    // Trigger native browser download by clicking an anchor tag
    const a = document.createElement('a');
    a.href = downloadUrl;
    a.download = ''; // "Without a value, the browser will suggest a filename/extension"
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click();
    a.remove();
    // wait 2 seconds to ensure the browser has shown that the download is starting.
    // a bit tricky to force this to happen earlier.
    await sleep(2000);
  } catch (error) {
    console.error('Multitrack download failed:', error);
    throw error;
  }
}
