import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ProgressBar } from './ProgressBar';

describe('ProgressBar', () => {
  const mockOnSeek = vi.fn();
  const defaultProps = {
    currentTime: 30,
    duration: 180,
    onSeek: mockOnSeek,
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should render progress bar', () => {
    render(<ProgressBar {...defaultProps} />);

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

  it('should display correct progress', () => {
    const { container } = render(<ProgressBar {...defaultProps} />);

    const progressTrack = container.querySelector('.bg-blue-600');
    const width = progressTrack
      ?.getAttribute('style')
      ?.match(/width:\s*([^;]+)/)?.[1];
    expect(width).toBeTruthy();
    if (width) {
      const widthValue = parseFloat(width);
      // 30/180 * 100 = 16.666...
      expect(widthValue).toBeGreaterThan(16.6);
      expect(widthValue).toBeLessThan(16.7);
    }
  });

  it('should have correct aria attributes', () => {
    render(<ProgressBar {...defaultProps} />);

    const slider = screen.getByRole('slider');
    expect(slider).toHaveAttribute('aria-label', 'Progression de la lecture');
    expect(slider).toHaveAttribute('aria-valuemin', '0');
    expect(slider).toHaveAttribute('aria-valuemax', '180');
    expect(slider).toHaveAttribute('aria-valuenow', '30');
  });

  it('should call onSeek when clicked', async () => {
    const { container } = render(<ProgressBar {...defaultProps} />);

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect to return known values
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    // Click at 50% of the progress bar (50px on a 100px wide bar)
    fireEvent.mouseDown(progressBar, {
      clientX: 50,
    });

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

    // Should seek to approximately 50% of duration (90 seconds)
    const seekCall = mockOnSeek.mock.calls[0][0];
    expect(seekCall).toBeGreaterThan(80);
    expect(seekCall).toBeLessThan(100);
  });

  it('should handle drag interaction', async () => {
    const { container } = render(<ProgressBar {...defaultProps} />);

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    // Start drag at 25%
    fireEvent.mouseDown(progressBar, { clientX: 25 });

    // Move to 75%
    fireEvent.mouseMove(document, { clientX: 75 });

    // End drag
    fireEvent.mouseUp(document);

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

  it('should show tooltip on hover', async () => {
    const { container } = render(
      <ProgressBar {...defaultProps} showTooltip={true} />,
    );

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    fireEvent.mouseEnter(progressBar, {
      clientX: 50,
    });

    fireEvent.mouseMove(progressBar, {
      clientX: 50,
    });

    await waitFor(() => {
      const tooltip = container.querySelector('.bg-gray-900');
      expect(tooltip).toBeInTheDocument();
    });
  });

  it('should hide tooltip when showTooltip is false', () => {
    const { container } = render(
      <ProgressBar {...defaultProps} showTooltip={false} />,
    );

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    fireEvent.mouseEnter(progressBar, {
      clientX: 50,
    });

    fireEvent.mouseMove(progressBar, {
      clientX: 50,
    });

    const tooltip = container.querySelector('.bg-gray-900');
    expect(tooltip).not.toBeInTheDocument();
  });

  it('should be disabled when disabled prop is true', () => {
    render(<ProgressBar {...defaultProps} disabled={true} />);

    const slider = screen.getByRole('slider');
    expect(slider).toHaveAttribute('aria-disabled', 'true');
    expect(slider).toHaveClass('cursor-not-allowed', 'opacity-50');
  });

  it('should not call onSeek when disabled', () => {
    const { container } = render(
      <ProgressBar {...defaultProps} disabled={true} />,
    );

    const progressBar = container.firstChild as HTMLElement;
    fireEvent.mouseDown(progressBar);

    expect(mockOnSeek).not.toHaveBeenCalled();
  });

  it('should handle zero duration', () => {
    const { container } = render(
      <ProgressBar currentTime={0} duration={0} onSeek={mockOnSeek} />,
    );

    const progressTrack = container.querySelector('.bg-blue-600');
    expect(progressTrack).toHaveStyle({ width: '0%' });
  });

  it('should handle currentTime greater than duration', () => {
    const { container } = render(
      <ProgressBar currentTime={200} duration={180} onSeek={mockOnSeek} />,
    );

    const progressTrack = container.querySelector('.bg-blue-600');
    // Should cap at 100% (or close to it due to calculation)
    const width = progressTrack
      ?.getAttribute('style')
      ?.match(/width:\s*([^;]+)/)?.[1];
    expect(width).toBeTruthy();
    if (width) {
      const widthValue = parseFloat(width);
      expect(widthValue).toBeLessThanOrEqual(100);
    }
  });

  it('should apply custom className', () => {
    const { container } = render(
      <ProgressBar {...defaultProps} className="custom-class" />,
    );

    expect(container.firstChild).toHaveClass('custom-class');
  });

  it('should show thumb on hover', () => {
    const { container } = render(<ProgressBar {...defaultProps} />);

    const progressBar = container.firstChild as HTMLElement;
    fireEvent.mouseEnter(progressBar);

    // The thumb should have group-hover:opacity-100 class, but we can't easily test CSS hover states
    // Instead, we verify the thumb element exists
    const thumb = container.querySelector('.w-4.h-4');
    expect(thumb).toBeInTheDocument();
  });

  it('should show thumb when dragging', () => {
    const { container } = render(<ProgressBar {...defaultProps} />);

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    fireEvent.mouseDown(progressBar, {
      clientX: 50,
    });

    // When dragging, the thumb should have opacity-100 class
    const thumb = container.querySelector('.w-4.h-4');
    expect(thumb).toBeInTheDocument();
    expect(thumb).toHaveClass('opacity-100');
  });

  it('should format tooltip time correctly', async () => {
    const { container } = render(
      <ProgressBar {...defaultProps} showTooltip={true} />,
    );

    const progressBar = container.firstChild as HTMLElement;

    // Mock getBoundingClientRect
    const mockRect = {
      left: 0,
      top: 0,
      width: 100,
      height: 10,
      bottom: 10,
      right: 100,
      x: 0,
      y: 0,
      toJSON: vi.fn(),
    };
    vi.spyOn(progressBar, 'getBoundingClientRect').mockReturnValue(
      mockRect as DOMRect,
    );

    // Hover at 50% (90 seconds = 1:30)
    fireEvent.mouseEnter(progressBar, {
      clientX: 50,
    });

    fireEvent.mouseMove(progressBar, {
      clientX: 50,
    });

    await waitFor(() => {
      const tooltip = container.querySelector('.bg-gray-900');
      expect(tooltip).toBeInTheDocument();
      expect(tooltip?.textContent).toMatch(/\d+:\d{2}/);
    });
  });

  it('should clean up event listeners on unmount', () => {
    const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener');
    const { unmount } = render(<ProgressBar {...defaultProps} />);

    const progressBar = screen.getByRole('slider');
    fireEvent.mouseDown(progressBar);
    fireEvent.mouseUp(document);

    unmount();

    // Verify that event listeners were removed
    expect(removeEventListenerSpy).toHaveBeenCalled();
  });
});
