import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TrackUpload } from './TrackUpload';
import {
  uploadTrack,
  getUploadProgress,
  TrackUploadError,
} from '../services/trackService';
import { useToast } from '@/hooks/useToast';

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

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

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useToast).mockReturnValue(mockToast);
  });

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

  it('should render upload area', () => {
    render(<TrackUpload />);

    expect(
      screen.getByText(/glissez-déposez un fichier audio/i),
    ).toBeInTheDocument();
    expect(screen.getByText(/sélectionner un fichier/i)).toBeInTheDocument();
  });

  it('should validate file format', async () => {
    const user = userEvent.setup();
    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const invalidFile = new File(['test'], 'test.txt', { type: 'text/plain' });

    Object.defineProperty(fileInput, 'files', {
      value: [invalidFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith(
        expect.stringContaining('Format non supporté'),
      );
    });
  });

  it('should validate file size', async () => {
    const user = userEvent.setup();
    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const largeFile = new File(['x'.repeat(101 * 1024 * 1024)], 'large.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [largeFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith(
        expect.stringContaining('Fichier trop volumineux'),
      );
    });
  });

  it('should upload valid file successfully', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: 'Test Artist',
      duration: 180,
      file_path: '/uploads/tracks/test.mp3',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'completed',
      progress: 100,
      message: 'Upload completed',
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(uploadTrack).toHaveBeenCalledWith(validFile, expect.any(Function));
    });

    // Wait for polling to complete
    await waitFor(
      () => {
        expect(getUploadProgress).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );
  });

  it('should show file preview after selection', async () => {
    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

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

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(screen.getByText('test.mp3')).toBeInTheDocument();
    });
  });

  it('should display progress bar during upload', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    let progressCallback: ((progress: number) => void) | undefined;

    vi.mocked(uploadTrack).mockImplementation(async (file, onProgress) => {
      progressCallback = onProgress;
      // Simulate progress
      if (onProgress) {
        onProgress(50);
      }
      return mockTrack;
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(screen.getByRole('progressbar')).toBeInTheDocument();
    });
  });

  it('should handle upload errors', async () => {
    const errorMessage = 'Upload failed';
    vi.mocked(uploadTrack).mockRejectedValue(new Error(errorMessage));

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith(errorMessage);
    });
  });

  it('should call onUploadComplete callback when upload completes', async () => {
    const onUploadComplete = vi.fn();
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'completed' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'completed',
      progress: 100,
      message: 'Upload completed',
    });

    render(<TrackUpload onUploadComplete={onUploadComplete} />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(
      () => {
        expect(getUploadProgress).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );

    // Wait for callback to be called
    await waitFor(
      () => {
        expect(onUploadComplete).toHaveBeenCalled();
      },
      { timeout: 5000 },
    );
  });

  it('should display upload speed and time remaining during upload', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 10 * 1024 * 1024, // 10MB
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    let progressCallback: ((progress: number) => void) | undefined;
    vi.mocked(uploadTrack).mockImplementation(async (file, onProgress) => {
      progressCallback = onProgress;
      // Simulate progress
      if (onProgress) {
        setTimeout(() => onProgress(50), 100);
        setTimeout(() => onProgress(100), 200);
      }
      return mockTrack;
    });

    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'completed',
      progress: 100,
      message: 'Upload completed',
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });
    Object.defineProperty(validFile, 'size', { value: 10 * 1024 * 1024 }); // 10MB

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(uploadTrack).toHaveBeenCalled();
    });

    // Wait for progress updates
    await waitFor(
      () => {
        // Check that progress bar is displayed
        expect(screen.getByRole('progressbar')).toBeInTheDocument();
      },
      { timeout: 500 },
    );
  });

  it('should display current step (uploading/processing)', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'processing' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'processing',
      progress: 100,
      message: 'Processing track',
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(
      () => {
        expect(getUploadProgress).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );
  });

  it('should handle drag and drop', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

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

    render(<TrackUpload />);

    const dropZone = screen
      .getByText(/glissez-déposez un fichier audio/i)
      .closest('div');
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    fireEvent.dragOver(dropZone!, {
      dataTransfer: {
        files: [validFile],
      },
    });

    fireEvent.drop(dropZone!, {
      dataTransfer: {
        files: [validFile],
      },
    });

    await waitFor(() => {
      expect(uploadTrack).toHaveBeenCalled();
    });
  });

  it('should validate empty file', async () => {
    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const emptyFile = new File([], 'empty.mp3', { type: 'audio/mpeg' });
    Object.defineProperty(emptyFile, 'size', { value: 0 });

    Object.defineProperty(fileInput, 'files', {
      value: [emptyFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith(
        expect.stringContaining('Le fichier est vide'),
      );
    });
  });

  it('should handle TrackUploadError with retry', async () => {
    const networkError = new TrackUploadError('Erreur réseau', 'NETWORK', true);

    // First attempt fails, second succeeds
    vi.mocked(uploadTrack)
      .mockRejectedValueOnce(networkError)
      .mockResolvedValueOnce({
        id: 1,
        creator_id: 123,
        title: 'test',
        artist: '',
        duration: 0,
        file_path: '',
        file_size: 1024,
        format: 'MP3',
        is_public: true,
        play_count: 0,
        like_count: 0,
        status: 'uploading' as const,
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
      });

    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'completed',
      progress: 100,
      message: 'Upload completed',
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    // Wait for retry
    await waitFor(
      () => {
        expect(mockToast.warning).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );

    // Eventually succeeds
    await waitFor(
      () => {
        expect(uploadTrack).toHaveBeenCalledTimes(2);
      },
      { timeout: 5000 },
    );
  });

  it('should handle TrackUploadError without retry (validation error)', async () => {
    const validationError = new TrackUploadError(
      'Format invalide',
      'VALIDATION',
      false,
    );

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

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith(
        expect.stringContaining('Format invalide'),
      );
    });

    // Should not retry
    expect(uploadTrack).toHaveBeenCalledTimes(1);
  });

  it('should display retry button after error', async () => {
    const error = new TrackUploadError('Erreur upload', 'SERVER', false);
    vi.mocked(uploadTrack).mockRejectedValue(error);

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(screen.getByText(/réessayer/i)).toBeInTheDocument();
    });
  });

  it('should reset upload state', async () => {
    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

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

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(() => {
      expect(screen.getByText('test.mp3')).toBeInTheDocument();
    });

    // Find and click reset button
    const resetButton = screen.getByRole('button', { name: /x/i });
    fireEvent.click(resetButton);

    await waitFor(() => {
      expect(screen.queryByText('test.mp3')).not.toBeInTheDocument();
    });
  });

  it('should handle polling errors gracefully', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
    vi.mocked(getUploadProgress).mockRejectedValue(new Error('Network error'));

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    // Should not crash even if polling fails
    await waitFor(
      () => {
        expect(getUploadProgress).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );
  });

  it('should handle failed status from polling', async () => {
    const mockTrack = {
      id: 1,
      creator_id: 123,
      title: 'test',
      artist: '',
      duration: 0,
      file_path: '',
      file_size: 1024,
      format: 'MP3',
      is_public: true,
      play_count: 0,
      like_count: 0,
      status: 'uploading' as const,
      created_at: '2024-01-01T00:00:00Z',
      updated_at: '2024-01-01T00:00:00Z',
    };

    vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
    vi.mocked(getUploadProgress).mockResolvedValue({
      track_id: 1,
      status: 'failed',
      progress: 0,
      message: 'Upload failed',
    });

    render(<TrackUpload />);

    const fileInput = document.querySelector(
      'input[type="file"]',
    ) as HTMLInputElement;
    const validFile = new File(['test audio'], 'test.mp3', {
      type: 'audio/mpeg',
    });

    Object.defineProperty(fileInput, 'files', {
      value: [validFile],
      writable: false,
    });

    fireEvent.change(fileInput);

    await waitFor(
      () => {
        expect(getUploadProgress).toHaveBeenCalled();
      },
      { timeout: 3000 },
    );

    // Should stop polling and show error
    await waitFor(
      () => {
        expect(screen.getByText(/upload failed/i)).toBeInTheDocument();
      },
      { timeout: 5000 },
    );
  });
});
