import { apiClient } from '@/services/api/client';
import { TokenStorage } from '@/services/tokenStorage';

/**
 * Track Download Service
 * T0314: Service frontend pour télécharger des tracks
 */

/**
 * Classe d'erreur personnalisée pour le téléchargement de tracks
 */
export class TrackDownloadError extends Error {
  constructor(
    message: string,
    public code:
      | 'VALIDATION'
      | 'NETWORK'
      | 'SERVER'
      | 'FORBIDDEN'
      | 'NOT_FOUND'
      | 'UNKNOWN',
    public retryable: boolean = false,
    public originalError?: unknown,
  ) {
    super(message);
    this.name = 'TrackDownloadError';
  }
}

/**
 * Options pour le téléchargement d'un track
 */
export interface DownloadTrackOptions {
  shareToken?: string;
  filename?: string;
  onProgress?: (progress: number) => void;
}

/**
 * Télécharge un track
 * @param trackId ID du track à télécharger
 * @param options Options de téléchargement (shareToken, filename, onProgress)
 * @throws TrackDownloadError si le téléchargement échoue
 */
export async function downloadTrack(
  trackId: number,
  options: DownloadTrackOptions = {},
): Promise<void> {
  const { shareToken, filename, onProgress } = options;

  try {
    // Construire l'URL avec le share token si fourni
    let url = `/tracks/${trackId}/download`;
    if (shareToken) {
      url += `?share_token=${encodeURIComponent(shareToken)}`;
    }

    // Obtenir le token d'authentification
    const token = TokenStorage.getAccessToken();
    const baseURL =
      apiClient.defaults.baseURL || 'http://localhost:8080/api/v1';
    const fullUrl = `${baseURL}${url}`;

    // Créer la requête fetch
    const headers: HeadersInit = {};
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }

    // Utiliser fetch pour télécharger le fichier avec support du progress
    const response = await fetch(fullUrl, {
      method: 'GET',
      headers,
    });

    if (!response.ok) {
      // Essayer de parser l'erreur JSON
      let errorMessage = 'Échec du téléchargement';
      try {
        const errorData = await response.json();
        errorMessage = errorData.error || errorMessage;
      } catch {
        // Si ce n'est pas du JSON, utiliser le message par défaut
      }

      if (response.status === 400) {
        throw new TrackDownloadError(errorMessage, 'VALIDATION', false);
      }
      if (response.status === 401) {
        throw new TrackDownloadError(
          'Non autorisé: Veuillez vous connecter pour télécharger ce track',
          'VALIDATION',
          false,
        );
      }
      if (response.status === 403) {
        throw new TrackDownloadError(
          errorMessage ||
            "Accès refusé: Vous n'avez pas la permission de télécharger ce track",
          'FORBIDDEN',
          false,
        );
      }
      if (response.status === 404) {
        throw new TrackDownloadError('Track introuvable', 'NOT_FOUND', false);
      }
      if (response.status === 500) {
        throw new TrackDownloadError(
          'Erreur serveur: Impossible de télécharger le track. Veuillez réessayer plus tard.',
          'SERVER',
          true,
        );
      }
      throw new TrackDownloadError(errorMessage, 'UNKNOWN', false);
    }

    // Gérer le téléchargement avec progress si supporté
    if (onProgress && response.body) {
      await downloadWithProgress(response, filename, onProgress);
    } else {
      // Téléchargement simple sans progress
      const blob = await response.blob();
      await triggerDownload(
        blob,
        filename || getFilenameFromResponse(response),
      );
    }
  } catch (error) {
    if (error instanceof TrackDownloadError) {
      throw error;
    }
    if (error instanceof TypeError && error.message.includes('fetch')) {
      throw new TrackDownloadError(
        'Erreur réseau: Impossible de se connecter au serveur. Veuillez vérifier votre connexion.',
        'NETWORK',
        true,
        error,
      );
    }
    throw new TrackDownloadError(
      'Erreur inconnue lors du téléchargement',
      'UNKNOWN',
      false,
      error,
    );
  }
}

/**
 * Télécharge un fichier avec suivi de progression
 */
async function downloadWithProgress(
  response: Response,
  filename: string | undefined,
  onProgress: (progress: number) => void,
): Promise<void> {
  const contentLength = response.headers.get('content-length');
  const total = contentLength ? parseInt(contentLength, 10) : 0;

  if (!response.body) {
    throw new TrackDownloadError('Response body is null', 'UNKNOWN', false);
  }

  const reader = response.body.getReader();
  const chunks: Uint8Array[] = [];
  let receivedLength = 0;

  try {
    while (true) {
      const { done, value } = await reader.read();

      if (done) {
        break;
      }

      chunks.push(value);
      receivedLength += value.length;

      // Mettre à jour la progression si on connaît la taille totale
      if (total > 0 && onProgress) {
        const progress = Math.round((receivedLength / total) * 100);
        onProgress(progress);
      }
    }

    // Combiner tous les chunks en un seul blob
    const allChunks = new Uint8Array(receivedLength);
    let position = 0;
    for (const chunk of chunks) {
      allChunks.set(chunk, position);
      position += chunk.length;
    }

    const blob = new Blob([allChunks], {
      type: response.headers.get('content-type') || 'application/octet-stream',
    });
    await triggerDownload(blob, filename || getFilenameFromResponse(response));
  } catch (error) {
    throw new TrackDownloadError(
      'Erreur lors du téléchargement avec progression',
      'NETWORK',
      true,
      error,
    );
  }
}

/**
 * Déclenche le téléchargement du fichier
 */
async function triggerDownload(blob: Blob, filename: string): Promise<void> {
  const downloadUrl = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = downloadUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  window.URL.revokeObjectURL(downloadUrl);
}

/**
 * Extrait le nom de fichier depuis les headers de la réponse
 */
function getFilenameFromResponse(response: Response): string {
  const contentDisposition = response.headers.get('content-disposition');
  if (contentDisposition) {
    const filenameMatch = contentDisposition.match(
      /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/,
    );
    if (filenameMatch && filenameMatch[1]) {
      let filename = filenameMatch[1].replace(/['"]/g, '');
      // Décoder les caractères encodés en URL
      try {
        filename = decodeURIComponent(filename);
      } catch {
        // Si le décodage échoue, utiliser le nom tel quel
      }
      return filename;
    }
  }
  return 'track';
}
