import { initiateChunkedUpload, uploadChunk, completeChunkedUpload } from './trackService';
import { Track } from '../types/track';

/**
 * Chunked Upload Service
 * T0262: Service pour gérer l'upload par chunks avec pause/resume et retry
 */

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB par chunk

export interface ChunkedUploadState {
  uploadId: string | null;
  file: File;
  chunks: Blob[];
  uploadedChunks: Set<number>;
  failedChunks: Set<number>;
  currentChunk: number;
  isPaused: boolean;
  isComplete: boolean;
  error: string | null;
}

export interface ChunkedUploadProgress {
  totalChunks: number;
  uploadedChunks: number;
  failedChunks: number;
  progress: number;
  currentChunk: number;
}

/**
 * Divise un fichier en chunks
 */
export function splitFileIntoChunks(file: File, chunkSize: number = CHUNK_SIZE): Blob[] {
  const chunks: Blob[] = [];
  let start = 0;

  while (start < file.size) {
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    chunks.push(chunk);
    start = end;
  }

  return chunks;
}

/**
 * Calcule le nombre de chunks nécessaires
 */
export function calculateTotalChunks(fileSize: number, chunkSize: number = CHUNK_SIZE): number {
  return Math.ceil(fileSize / chunkSize);
}

/**
 * Gère l'upload par chunks avec pause/resume et retry
 */
export class ChunkedUploadManager {
  private state: ChunkedUploadState;
  private onProgress?: (progress: ChunkedUploadProgress) => void;
  private maxRetries: number = 3;
  private retryDelay: number = 1000; // 1 seconde

  constructor(file: File, onProgress?: (progress: ChunkedUploadProgress) => void) {
    const chunks = splitFileIntoChunks(file);
    this.state = {
      uploadId: null,
      file,
      chunks,
      uploadedChunks: new Set<number>(),
      failedChunks: new Set<number>(),
      currentChunk: 0,
      isPaused: false,
      isComplete: false,
      error: null,
    };
    this.onProgress = onProgress;
  }

  /**
   * Démarre l'upload
   */
  async start(): Promise<void> {
    if (this.state.uploadId) {
      // Resume existing upload (ne pas réinitier, juste uploader les chunks restants)
      await this.uploadRemainingChunks();
    } else {
      // Start new upload
      await this.initiateUpload();
      await this.uploadRemainingChunks();
    }
  }

  /**
   * Définit l'uploadId (pour reprise)
   */
  setUploadId(uploadId: string): void {
    this.state.uploadId = uploadId;
  }

  /**
   * Met en pause l'upload
   */
  pause(): void {
    this.state.isPaused = true;
  }

  /**
   * Reprend l'upload
   */
  async resume(): Promise<void> {
    if (this.state.isPaused && !this.state.isComplete) {
      this.state.isPaused = false;
      await this.uploadRemainingChunks();
    }
  }

  /**
   * Annule l'upload
   */
  cancel(): void {
    this.state.isPaused = true;
    this.state.error = 'Upload cancelled';
  }

  /**
   * Récupère l'état actuel
   */
  getState(): ChunkedUploadState {
    return { ...this.state };
  }

  /**
   * Initie l'upload côté serveur
   */
  private async initiateUpload(): Promise<void> {
    const totalChunks = this.state.chunks.length;
    const uploadId = await initiateChunkedUpload(
      totalChunks,
      this.state.file.size,
      this.state.file.name
    );
    this.state.uploadId = uploadId;
  }

  /**
   * Upload les chunks restants
   */
  private async uploadRemainingChunks(): Promise<void> {
    if (this.state.isPaused || this.state.isComplete) {
      return;
    }

    const totalChunks = this.state.chunks.length;

    // Upload tous les chunks (séquentiellement)
    for (let i = 0; i < totalChunks; i++) {
      const chunkNumber = i + 1; // 1-indexed

      // Vérifier si on est en pause
      if (this.state.isPaused) {
        break;
      }

      // Vérifier si le chunk est déjà uploadé
      if (this.state.uploadedChunks.has(chunkNumber)) {
        continue;
      }

      // Retry les chunks échoués
      if (this.state.failedChunks.has(chunkNumber)) {
        const success = await this.uploadChunkWithRetry(chunkNumber);
        if (!success) {
          this.state.error = `Failed to upload chunk ${chunkNumber} after ${this.maxRetries} retries`;
          return;
        }
      } else {
        // Upload normal
        const success = await this.uploadChunkWithRetry(chunkNumber);
        if (!success) {
          this.state.error = `Failed to upload chunk ${chunkNumber} after ${this.maxRetries} retries`;
          return;
        }
      }

      this.state.currentChunk = chunkNumber;
      this.updateProgress();
    }

    // Si tous les chunks sont uploadés, compléter l'upload
    if (this.state.uploadedChunks.size === totalChunks && !this.state.isPaused) {
      await this.completeUpload();
    }
  }

  /**
   * Upload un chunk avec retry
   */
  private async uploadChunkWithRetry(chunkNumber: number): Promise<boolean> {
    const chunk = this.state.chunks[chunkNumber - 1];
    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        await uploadChunk(
          this.state.uploadId!,
          chunkNumber,
          this.state.chunks.length,
          this.state.file.size,
          this.state.file.name,
          chunk,
          (progress) => {
            // Progress individuel du chunk (non utilisé pour l'instant)
          }
        );

        // Succès
        this.state.uploadedChunks.add(chunkNumber);
        this.state.failedChunks.delete(chunkNumber);
        return true;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        
        // Si ce n'est pas la dernière tentative, attendre avant de retry
        if (attempt < this.maxRetries) {
          await this.delay(this.retryDelay * attempt); // Exponential backoff
        }
      }
    }

    // Toutes les tentatives ont échoué
    this.state.failedChunks.add(chunkNumber);
    this.state.error = lastError?.message || 'Unknown error';
    return false;
  }

  /**
   * Complète l'upload
   */
  private async completeUpload(): Promise<void> {
    if (!this.state.uploadId) {
      throw new Error('No upload ID available');
    }

    try {
      await completeChunkedUpload(this.state.uploadId);
      this.state.isComplete = true;
      this.updateProgress();
    } catch (error) {
      this.state.error = error instanceof Error ? error.message : 'Failed to complete upload';
      throw error;
    }
  }

  /**
   * Met à jour la progression
   */
  private updateProgress(): void {
    const totalChunks = this.state.chunks.length;
    const uploadedChunks = this.state.uploadedChunks.size;
    const failedChunks = this.state.failedChunks.size;
    const progress = Math.round((uploadedChunks / totalChunks) * 100);

    if (this.onProgress) {
      this.onProgress({
        totalChunks,
        uploadedChunks,
        failedChunks,
        progress,
        currentChunk: this.state.currentChunk,
      });
    }
  }

  /**
   * Délai pour le retry
   */
  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

/**
 * Upload un fichier par chunks
 */
export async function uploadFileInChunks(
  file: File,
  onProgress?: (progress: ChunkedUploadProgress) => void
): Promise<Track> {
  const manager = new ChunkedUploadManager(file, onProgress);
  
  await manager.start();

  if (manager.getState().error) {
    throw new Error(manager.getState().error!);
  }

  if (!manager.getState().isComplete) {
    throw new Error('Upload not completed');
  }

  // Retourner un track partiel (l'ID sera récupéré via le callback ou une autre méthode)
  // Pour l'instant, on retourne un objet Track minimal
  return {
    id: 0,
    user_id: 0,
    title: file.name.replace(/\.[^/.]+$/, ''),
    artist: '',
    duration: 0,
    file_path: '',
    file_size: file.size,
    format: file.name.split('.').pop()?.toUpperCase() || 'UNKNOWN',
    is_public: true,
    play_count: 0,
    like_count: 0,
    status: 'completed',
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
  };
}

