import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TrackDownloadButton } from './TrackDownloadButton';
import {
  downloadTrack,
  TrackDownloadError,
} from '../services/trackDownloadService';
import { useToast } from '@/hooks/useToast';

// Mock dependencies
vi.mock('../services/trackDownloadService');
vi.mock('@/hooks/useToast');

describe('TrackDownloadButton', () => {
  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);
  });

  it('should render download button', () => {
    render(<TrackDownloadButton trackId={123} />);

    expect(screen.getByText('Télécharger')).toBeInTheDocument();
    expect(screen.getByRole('button')).toBeInTheDocument();
  });

  it('should download track when button is clicked', async () => {
    vi.mocked(downloadTrack).mockResolvedValue(undefined);

    render(<TrackDownloadButton trackId={123} />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(downloadTrack).toHaveBeenCalledWith(123, {
        shareToken: undefined,
        filename: undefined,
        onProgress: undefined,
      });
    });

    expect(mockToast.success).toHaveBeenCalledWith('Téléchargement terminé');
  });

  it('should download track with share token', async () => {
    vi.mocked(downloadTrack).mockResolvedValue(undefined);

    render(<TrackDownloadButton trackId={123} shareToken="test-token" />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(downloadTrack).toHaveBeenCalledWith(123, {
        shareToken: 'test-token',
        filename: undefined,
        onProgress: undefined,
      });
    });
  });

  it('should show loading state during download', async () => {
    let resolveDownload: () => void;
    const downloadPromise = new Promise<void>((resolve) => {
      resolveDownload = resolve;
    });
    vi.mocked(downloadTrack).mockReturnValue(downloadPromise);

    render(<TrackDownloadButton trackId={123} />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(screen.getByText(/Téléchargement.../i)).toBeInTheDocument();
    });

    expect(button).toBeDisabled();

    // Résoudre le téléchargement
    resolveDownload!();
    await waitFor(() => {
      expect(screen.getByText('Télécharger')).toBeInTheDocument();
    });
  });

  it('should show progress bar when showProgress is true', async () => {
    let progressCallback: (progress: number) => void;
    const downloadPromise = new Promise<void>((resolve) => {
      vi.mocked(downloadTrack).mockImplementation((id, options) => {
        if (options?.onProgress) {
          progressCallback = options.onProgress;
        }
        setTimeout(() => resolve(), 100);
        return Promise.resolve();
      });
    });

    render(<TrackDownloadButton trackId={123} showProgress={true} />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

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

    // Simuler la progression
    if (progressCallback!) {
      progressCallback(50);
      await waitFor(() => {
        expect(screen.getByText(/50%/i)).toBeInTheDocument();
      });
    }
  });

  it('should display error message on download failure', async () => {
    const error = new TrackDownloadError('Download failed', 'NETWORK', true);
    vi.mocked(downloadTrack).mockRejectedValue(error);

    render(<TrackDownloadButton trackId={123} />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(screen.getByText('Download failed')).toBeInTheDocument();
    });

    expect(mockToast.error).toHaveBeenCalledWith('Download failed');
  });

  it('should call onDownloadStart callback', async () => {
    const onDownloadStart = vi.fn();
    vi.mocked(downloadTrack).mockResolvedValue(undefined);

    render(
      <TrackDownloadButton trackId={123} onDownloadStart={onDownloadStart} />,
    );

    const button = screen.getByRole('button');
    await userEvent.click(button);

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

  it('should call onDownloadComplete callback', async () => {
    const onDownloadComplete = vi.fn();
    vi.mocked(downloadTrack).mockResolvedValue(undefined);

    render(
      <TrackDownloadButton
        trackId={123}
        onDownloadComplete={onDownloadComplete}
      />,
    );

    const button = screen.getByRole('button');
    await userEvent.click(button);

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

  it('should call onDownloadError callback on failure', async () => {
    const onDownloadError = vi.fn();
    const error = new TrackDownloadError('Download failed', 'NETWORK', true);
    vi.mocked(downloadTrack).mockRejectedValue(error);

    render(
      <TrackDownloadButton trackId={123} onDownloadError={onDownloadError} />,
    );

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(onDownloadError).toHaveBeenCalledWith('Download failed');
    });
  });

  it('should use custom filename if provided', async () => {
    vi.mocked(downloadTrack).mockResolvedValue(undefined);

    render(<TrackDownloadButton trackId={123} filename="custom-track.mp3" />);

    const button = screen.getByRole('button');
    await userEvent.click(button);

    await waitFor(() => {
      expect(downloadTrack).toHaveBeenCalledWith(123, {
        shareToken: undefined,
        filename: 'custom-track.mp3',
        onProgress: undefined,
      });
    });
  });

  it('should apply custom variant and size', () => {
    render(<TrackDownloadButton trackId={123} variant="outline" size="sm" />);

    const button = screen.getByRole('button');
    expect(button).toBeInTheDocument();
  });
});
