import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  splitFileIntoChunks,
  calculateTotalChunks,
  ChunkedUploadManager,
  CHUNK_SIZE,
} from './chunkedUploadService';
import * as trackApi from '../api/trackApi';

// Mock trackApi
vi.mock('../api/trackApi');

describe('chunkedUploadService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('splitFileIntoChunks', () => {
    it('should split file into chunks of correct size', () => {
      const file = new File(['x'.repeat(12 * 1024 * 1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });
      const chunks = splitFileIntoChunks(file, 5 * 1024 * 1024);

      expect(chunks.length).toBe(3); // 12MB / 5MB = 3 chunks
      expect(chunks[0].size).toBe(5 * 1024 * 1024);
      expect(chunks[1].size).toBe(5 * 1024 * 1024);
      expect(chunks[2].size).toBe(2 * 1024 * 1024);
    });

    it('should handle file smaller than chunk size', () => {
      const file = new File(['x'.repeat(1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });
      const chunks = splitFileIntoChunks(file, 5 * 1024 * 1024);

      expect(chunks.length).toBe(1);
      expect(chunks[0].size).toBe(1024);
    });
  });

  describe('calculateTotalChunks', () => {
    it('should calculate correct number of chunks', () => {
      expect(calculateTotalChunks(12 * 1024 * 1024, 5 * 1024 * 1024)).toBe(3);
      expect(calculateTotalChunks(10 * 1024 * 1024, 5 * 1024 * 1024)).toBe(2);
      expect(calculateTotalChunks(1024, 5 * 1024 * 1024)).toBe(1);
    });
  });

  describe('ChunkedUploadManager', () => {
    it('should initiate upload and upload chunks sequentially', async () => {
      const file = new File(['x'.repeat(10 * 1024 * 1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });
      const onProgress = vi.fn();

      vi.mocked(trackApi.initiateChunkedUpload).mockResolvedValue('upload-123');
      let chunkCallCount = 0;
      vi.mocked(trackApi.uploadChunk).mockImplementation(async () => {
        chunkCallCount++;
        return {
          message: 'Chunk uploaded',
          upload_id: 'upload-123',
          received_chunks: chunkCallCount,
          total_chunks: 2,
          progress: (chunkCallCount / 2) * 100,
        };
      });
      vi.mocked(trackApi.completeChunkedUpload).mockResolvedValue({
        id: 1,
        creator_id: 123,
        title: 'test',
        artist: '',
        duration: 0,
        file_path: '',
        file_size: 10 * 1024 * 1024,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        status: 'completed',
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
      });

      const manager = new ChunkedUploadManager(file, onProgress);
      await manager.start();

      expect(trackApi.initiateChunkedUpload).toHaveBeenCalledWith(
        2,
        10 * 1024 * 1024,
        'test.mp3',
      );
      expect(trackApi.uploadChunk).toHaveBeenCalledTimes(2);
      expect(trackApi.completeChunkedUpload).toHaveBeenCalledWith('upload-123');
      expect(manager.getState().isComplete).toBe(true);
    });

    it('should pause and resume upload', async () => {
      const file = new File(['x'.repeat(10 * 1024 * 1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });

      vi.mocked(trackApi.initiateChunkedUpload).mockResolvedValue('upload-123');
      let chunkCallCount = 0;
      vi.mocked(trackApi.uploadChunk).mockImplementation(async () => {
        await new Promise((resolve) => setTimeout(resolve, 100));
        chunkCallCount++;
        return {
          message: 'Chunk uploaded',
          upload_id: 'upload-123',
          received_chunks: chunkCallCount,
          total_chunks: 2,
          progress: (chunkCallCount / 2) * 100,
        };
      });
      vi.mocked(trackApi.completeChunkedUpload).mockResolvedValue({
        id: 1,
        creator_id: 123,
        title: 'test',
        artist: '',
        duration: 0,
        file_path: '',
        file_size: 10 * 1024 * 1024,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        status: 'completed',
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
      });

      const manager = new ChunkedUploadManager(file);

      // Start upload
      const startPromise = manager.start();

      // Pause immediately
      await new Promise((resolve) => setTimeout(resolve, 50));
      manager.pause();

      expect(manager.getState().isPaused).toBe(true);

      // Resume
      await manager.resume();
      await startPromise;

      expect(manager.getState().isComplete).toBe(true);
    });

    it('should retry failed chunks', async () => {
      const file = new File(['x'.repeat(5 * 1024 * 1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });

      vi.mocked(trackApi.initiateChunkedUpload).mockResolvedValue('upload-123');

      // First two attempts fail, third succeeds
      let attemptCount = 0;
      vi.mocked(trackApi.uploadChunk).mockImplementation(async () => {
        attemptCount++;
        if (attemptCount < 3) {
          throw new Error('Network error');
        }
        return {
          message: 'Chunk uploaded',
          upload_id: 'upload-123',
          received_chunks: 1,
          total_chunks: 1,
          progress: 100,
        };
      });

      vi.mocked(trackApi.completeChunkedUpload).mockResolvedValue({
        id: 1,
        creator_id: 123,
        title: 'test',
        artist: '',
        duration: 0,
        file_path: '',
        file_size: 5 * 1024 * 1024,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        status: 'completed',
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
      });

      const manager = new ChunkedUploadManager(file);
      await manager.start();

      expect(trackApi.uploadChunk).toHaveBeenCalledTimes(3); // 2 retries + 1 success
      expect(manager.getState().isComplete).toBe(true);
    });

    it('should cancel upload', () => {
      const file = new File(['x'.repeat(10 * 1024 * 1024)], 'test.mp3', {
        type: 'audio/mpeg',
      });
      const manager = new ChunkedUploadManager(file);

      manager.cancel();

      const state = manager.getState();
      expect(state.isPaused).toBe(true);
      expect(state.error).toBe('Upload cancelled');
    });
  });
});
