/**
 * Integration tests for track upload flow
 * FE-TEST-010: Test complete track upload and processing
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import {
  uploadTrack,
  getUploadProgress,
  TrackUploadError,
} from '../services/trackService';
import { uploadTrack as uploadTrackApi } from '../api/trackApi';
import { useToast } from '@/hooks/useToast';
import { useAuthStore } from '@/features/auth/store/authStore';

// Mock dependencies
vi.mock('../services/trackService', () => ({
  uploadTrack: vi.fn(),
  getUploadProgress: vi.fn(),
  TrackUploadError: class extends Error {
    constructor(
      message: string,
      public code: string,
      public retryable: boolean = false,
    ) {
      super(message);
    }
  },
}));

vi.mock('../api/trackApi', () => ({
  uploadTrack: vi.fn(),
}));

vi.mock('@/hooks/useToast', () => ({
  useToast: vi.fn(),
}));

vi.mock('@/features/auth/store/authStore', () => ({
  useAuthStore: vi.fn(),
}));

vi.mock('../services/chunkedUploadService', () => ({
  ChunkedUploadManager: vi.fn(),
  calculateTotalChunks: vi.fn((size) => Math.ceil(size / (5 * 1024 * 1024))),
  CHUNK_SIZE: 5 * 1024 * 1024,
}));

const createTestQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

const TestWrapper = ({ children }: { children: React.ReactNode }) => {
  const queryClient = createTestQueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <MemoryRouter>{children}</MemoryRouter>
    </QueryClientProvider>
  );
};

describe('Track Upload Integration Tests', () => {
  const mockToast = {
    success: vi.fn(),
    error: vi.fn(),
    warning: vi.fn(),
    info: vi.fn(),
    toast: vi.fn(),
  };

  const mockUser = {
    id: '1',
    email: 'test@example.com',
    username: 'testuser',
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useToast).mockReturnValue(mockToast as any);
    vi.mocked(useAuthStore).mockReturnValue({
      user: mockUser,
      isAuthenticated: true,
    } as any);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('Complete Upload Flow', () => {
    it('should complete full upload flow with valid audio file', async () => {
      const mockTrack = {
        id: '1',
        user_id: '1',
        title: 'Test Track',
        artist: 'Test Artist',
        duration: 180,
        file_path: '/tracks/1.mp3',
        file_size: 5000000,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };

      vi.mocked(uploadTrack).mockResolvedValue(mockTrack as any);

      // Test the upload service directly
      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      const result = await uploadTrack(audioFile, progressCallback);

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
      expect(result).toEqual(mockTrack);
    });

    it('should handle upload with metadata using trackApi', async () => {
      const mockTrack = {
        id: '1',
        user_id: '1',
        title: 'Custom Title',
        artist: 'Custom Artist',
        duration: 180,
        file_path: '/tracks/1.mp3',
        file_size: 5000000,
        format: 'MP3',
        is_public: false,
        play_count: 0,
        like_count: 0,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };

      vi.mocked(uploadTrackApi).mockResolvedValue(mockTrack as any);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const metadata = {
        title: 'Custom Title',
        artist: 'Custom Artist',
        is_public: false,
      };

      const progressCallback = vi.fn();
      const result = await uploadTrackApi(
        audioFile,
        metadata,
        progressCallback,
      );

      expect(uploadTrackApi).toHaveBeenCalledWith(
        audioFile,
        metadata,
        expect.any(Function),
      );
      expect(result).toEqual(mockTrack);
    });

    it('should show upload progress during file upload', async () => {
      const mockTrack = {
        id: '1',
        user_id: '1',
        title: 'Test Track',
        duration: 180,
        file_path: '/tracks/1.mp3',
        file_size: 5000000,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };

      // Mock upload with progress callback
      const progressCallback = vi.fn();
      vi.mocked(uploadTrack).mockImplementation(async (file, onProgress) => {
        // Simulate progress updates
        if (onProgress) {
          onProgress(25);
          onProgress(50);
          onProgress(75);
          onProgress(100);
        }
        return mockTrack as any;
      });

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      await uploadTrack(audioFile, progressCallback);

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
      // Progress callback should have been called
      expect(progressCallback).toHaveBeenCalled();
    });

    it('should handle upload errors gracefully', async () => {
      const error = new TrackUploadError(
        'Upload failed: File too large',
        'VALIDATION',
        false,
      );

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      await expect(uploadTrack(audioFile, progressCallback)).rejects.toThrow(
        'Upload failed: File too large',
      );

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
    });

    it('should validate file format before upload', async () => {
      const invalidFile = new File(['content'], 'test.txt', {
        type: 'text/plain',
      });

      const error = new TrackUploadError(
        'Format non supporté',
        'VALIDATION',
        false,
      );

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const progressCallback = vi.fn();
      await expect(uploadTrack(invalidFile, progressCallback)).rejects.toThrow(
        'Format non supporté',
      );

      expect(uploadTrack).toHaveBeenCalledWith(
        invalidFile,
        expect.any(Function),
      );
    });

    it(
      'should validate file size before upload',
      async () => {
        // Create a file larger than 100MB (simplified for test performance)
        const largeFile = new File(
          ['x'.repeat(10 * 1024)], // Smaller size for test performance
          'large.mp3',
          { type: 'audio/mpeg' },
        );

        // Mock file size to simulate large file
        Object.defineProperty(largeFile, 'size', {
          value: 101 * 1024 * 1024,
          writable: false,
        });

        const error = new TrackUploadError(
          'Fichier trop volumineux',
          'VALIDATION',
          false,
        );

        vi.mocked(uploadTrack).mockRejectedValue(error);

        const progressCallback = vi.fn();
        await expect(uploadTrack(largeFile, progressCallback)).rejects.toThrow(
          'Fichier trop volumineux',
        );

        expect(uploadTrack).toHaveBeenCalledWith(
          largeFile,
          expect.any(Function),
        );
      },
      { timeout: 10000 },
    );

    it('should handle async upload with status polling', async () => {
      const mockTrack = {
        id: '1',
        user_id: '1',
        title: 'Test Track',
        duration: 180,
        file_path: '/tracks/1.mp3',
        file_size: 5000000,
        format: 'MP3',
        status: 'completed',
        is_public: true,
        play_count: 0,
        like_count: 0,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      };

      // Mock async upload that polls status
      vi.mocked(uploadTrackApi).mockResolvedValue(mockTrack as any);
      vi.mocked(getUploadProgress).mockResolvedValue({
        track_id: '1',
        status: 'completed',
        progress: 100,
        message: 'Upload completed',
      } as any);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      const result = await uploadTrackApi(audioFile, {}, progressCallback);

      expect(uploadTrackApi).toHaveBeenCalledWith(
        audioFile,
        {},
        expect.any(Function),
      );
      expect(result).toEqual(mockTrack);
    });

    it('should handle network errors during upload', async () => {
      const error = new TrackUploadError(
        'Network error: Failed to connect',
        'NETWORK',
        true,
      );

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      await expect(uploadTrack(audioFile, progressCallback)).rejects.toThrow(
        'Network error: Failed to connect',
      );

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
    });

    it('should handle server errors during upload', async () => {
      const error = new TrackUploadError(
        'Server error: Internal server error',
        'SERVER',
        false,
      );

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      await expect(uploadTrack(audioFile, progressCallback)).rejects.toThrow(
        'Server error: Internal server error',
      );

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
    });

    it('should handle quota exceeded error', async () => {
      const error = new TrackUploadError('Quota exceeded', 'QUOTA', false);

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      await expect(uploadTrack(audioFile, progressCallback)).rejects.toThrow(
        'Quota exceeded',
      );

      expect(uploadTrack).toHaveBeenCalledWith(audioFile, expect.any(Function));
    });

    it('should handle retryable errors', async () => {
      const error = new TrackUploadError(
        'Network timeout',
        'NETWORK',
        true, // retryable
      );

      vi.mocked(uploadTrack).mockRejectedValue(error);

      const audioFile = new File(['audio content'], 'test.mp3', {
        type: 'audio/mpeg',
      });

      const progressCallback = vi.fn();
      await expect(uploadTrack(audioFile, progressCallback)).rejects.toThrow(
        'Network timeout',
      );
      expect(error.retryable).toBe(true);
    });
  });
});
