// src/features/flyer/BulkImporter.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BulkImporter } from './BulkImporter';

describe('BulkImporter', () => {
  const mockOnFilesChange = vi.fn();
  const mockCreateObjectURL = vi.fn();
  const mockRevokeObjectURL = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    // Mock the global URL object methods
    global.URL.createObjectURL = mockCreateObjectURL;
    global.URL.revokeObjectURL = mockRevokeObjectURL;
  });

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

  it('should render the initial state correctly', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    expect(screen.getByText(/click to upload/i)).toBeInTheDocument();
    expect(screen.getByText(/drag and drop/i)).toBeInTheDocument();
    expect(screen.getByText(/png, jpg, webp, or pdf/i)).toBeInTheDocument();
  });

  it('should render the processing state', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={true} />);
    expect(screen.getByText(/processing, please wait.../i)).toBeInTheDocument();
    const label = screen.getByText(/processing, please wait.../i).closest('label');
    expect(label).toHaveClass('cursor-not-allowed');
    expect(label).toHaveClass('opacity-60');
  });

  it('should call onFilesChange when files are selected via input', async () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to upload/i);

    await waitFor(() =>
      fireEvent.change(input, {
        target: { files: [file] },
      }),
    );

    expect(mockOnFilesChange).toHaveBeenCalledTimes(1);
    expect(mockOnFilesChange.mock.calls[0][0][0]).toBe(file);
  });

  it('should handle drag and drop events', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    // `getByLabelText` finds the input associated with the label.
    // We need to test the label itself, as it contains the drag-and-drop handlers and styles.
    // We can find it by its text content.
    const dropzone = screen.getByText(/click to upload/i).closest('label');
    // Assert that the dropzone element was found before using it.
    // This satisfies TypeScript's null-check and makes the test more robust.
    expect(dropzone).not.toBeNull();

    // Test drag enter
    // We need to assert that dropzone is not null before passing it to fireEvent.
    if (!dropzone) throw new Error('Dropzone not found');
    fireEvent.dragEnter(dropzone, { dataTransfer: { files: [] } });
    expect(dropzone).toHaveClass('border-brand-primary');

    // Test drag leave
    // We need to assert that dropzone is not null before passing it to fireEvent.
    if (!dropzone) throw new Error('Dropzone not found');
    fireEvent.dragLeave(dropzone);
    expect(dropzone).not.toHaveClass('border-brand-primary');
  });

  it('should call onFilesChange when files are dropped', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    // The drop event handlers are on the <label>, not the <input>
    const dropzone = screen.getByText(/click to upload/i).closest('label')!;
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });

    fireEvent.drop(dropzone, {
      dataTransfer: {
        files: [file],
        items: [file], // Also mock the 'items' property for full DataTransfer simulation
      },
    });

    expect(mockOnFilesChange).toHaveBeenCalledTimes(1);
    expect(mockOnFilesChange.mock.calls[0][0][0]).toBe(file);
  });

  it('should not call onFilesChange if no files are dropped', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    // The drop event handlers are on the <label>, not the <input>
    const dropzone = screen.getByText(/click to upload/i).closest('label')!;

    fireEvent.drop(dropzone, {
      dataTransfer: {
        files: [],
      },
    });

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

  it('should not respond to interactions when isProcessing is true', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={true} />);
    const dropzone = screen.getByLabelText(/processing, please wait.../i);

    fireEvent.dragEnter(dropzone, { dataTransfer: { files: [] } });
    expect(dropzone).not.toHaveClass('border-brand-primary');
  });

  it('should not call onFilesChange when files are dropped while isProcessing is true', () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={true} />);
    const dropzone = screen.getByText(/processing, please wait.../i).closest('label')!;
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });

    fireEvent.drop(dropzone, {
      dataTransfer: {
        files: [file],
      },
    });

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

  it('should handle file input change with null files', async () => {
    render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
    const input = screen.getByLabelText(/click to upload/i);

    // Simulate a change event with null files (e.g., when user cancels file picker)
    fireEvent.change(input, { target: { files: null } });

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

  describe('when files are selected', () => {
    const imageFile = new File(['image-content'], 'flyer.jpg', { type: 'image/jpeg' });
    const pdfFile = new File(['pdf-content'], 'document.pdf', { type: 'application/pdf' });

    it('should display file previews and distinguish between images and other file types', async () => {
      mockCreateObjectURL
        .mockReturnValueOnce('blob:http://localhost/image-guid') // For the image file
        .mockReturnValueOnce(''); // For the PDF file

      render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
      const input = screen.getByLabelText(/click to upload/i);

      await act(async () => {
        fireEvent.change(input, { target: { files: [imageFile, pdfFile] } });
      });

      // Wait for previews to render
      await waitFor(() => {
        expect(screen.getByText('flyer.jpg')).toBeInTheDocument();
        expect(screen.getByText('document.pdf')).toBeInTheDocument();
      });

      // Check that an image preview is rendered for the image file
      const imagePreview = screen.getByAltText('flyer.jpg');
      expect(imagePreview).toBeInTheDocument();
      expect(imagePreview).toHaveAttribute('src', 'blob:http://localhost/image-guid');

      // Check that a generic document icon is rendered for the PDF
      const pdfPreviewContainer = screen
        .getByText('document.pdf')
        .closest('div.relative') as HTMLElement;
      // The icon itself doesn't have a test-id, but we can find it by its role and class.
      expect(pdfPreviewContainer.querySelector('svg.w-12.h-12')).toBeInTheDocument();
    });

    it('should allow removing a file from the preview list', async () => {
      render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
      const input = screen.getByLabelText(/click to upload/i);

      await act(async () => {
        fireEvent.change(input, { target: { files: [imageFile, pdfFile] } });
      });

      // Wait for previews to appear and check initial state
      await screen.findByText('flyer.jpg');
      expect(screen.getByText('document.pdf')).toBeInTheDocument();
      expect(mockOnFilesChange).toHaveBeenLastCalledWith([imageFile, pdfFile]);

      // Find and click the remove button for the first file ('flyer.jpg')
      const removeButton = screen.getByLabelText(`Remove ${imageFile.name}`);
      await act(async () => {
        fireEvent.click(removeButton);
      });

      // Assert that the file was removed from the display and the parent was notified
      expect(screen.queryByText('flyer.jpg')).not.toBeInTheDocument();
      expect(screen.getByText('document.pdf')).toBeInTheDocument(); // The other file should remain
      expect(mockOnFilesChange).toHaveBeenLastCalledWith([pdfFile]);
    });

    it('should prevent adding duplicate files', async () => {
      render(<BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />);
      const input = screen.getByLabelText(/click to upload/i);

      // Add the file for the first time
      await act(async () => {
        fireEvent.change(input, { target: { files: [imageFile] } });
      });
      expect(mockOnFilesChange).toHaveBeenCalledTimes(1);
      expect(mockOnFilesChange).toHaveBeenLastCalledWith([imageFile]);

      // Attempt to add the exact same file again
      await act(async () => {
        fireEvent.change(input, { target: { files: [imageFile] } });
      });
      // The number of calls should NOT increase because the file is a duplicate
      expect(mockOnFilesChange).toHaveBeenCalledTimes(1);
    });

    it('should revoke object URLs on cleanup', async () => {
      mockCreateObjectURL.mockReturnValue('blob:http://localhost/test-url');
      const { unmount } = render(
        <BulkImporter onFilesChange={mockOnFilesChange} isProcessing={false} />,
      );
      const input = screen.getByLabelText(/click to upload/i);

      await act(async () => {
        fireEvent.change(input, { target: { files: [imageFile] } });
      });

      await waitFor(() => {
        expect(mockCreateObjectURL).toHaveBeenCalledWith(imageFile);
      });

      // Unmount the component to trigger the cleanup effect
      unmount();

      expect(mockRevokeObjectURL).toHaveBeenCalledTimes(1);
      expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:http://localhost/test-url');
    });
  });
});
