import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { screen, fireEvent, waitFor, act } from '@testing-library/react';
import SyncProgressDisplay from '../SyncProgressDisplay';
import { renderWithProviders } from '../../test/test-utils';
// Define SyncProgressInfo type locally for tests
interface SyncProgressInfo {
  source_id: string;
  phase: string;
  phase_description: string;
  elapsed_time_secs: number;
  directories_found: number;
  directories_processed: number;
  files_found: number;
  files_processed: number;
  bytes_processed: number;
  processing_rate_files_per_sec: number;
  files_progress_percent: number;
  estimated_time_remaining_secs?: number;
  current_directory: string;
  current_file?: string;
  errors: number;
  warnings: number;
  is_active: boolean;
}

// Mock the API module using the __mocks__ version
vi.mock('../../services/api');

// Import the mock helpers
import { getMockEventSource, resetMockEventSource } from '../../services/__mocks__/api';

// Create mock progress data factory
const createMockProgressInfo = (overrides: Partial<SyncProgressInfo> = {}): SyncProgressInfo => ({
  source_id: 'test-source-123',
  phase: 'processing_files',
  phase_description: 'Downloading and processing files',
  elapsed_time_secs: 120,
  directories_found: 10,
  directories_processed: 7,
  files_found: 50,
  files_processed: 30,
  bytes_processed: 1024000,
  processing_rate_files_per_sec: 2.5,
  files_progress_percent: 60.0,
  estimated_time_remaining_secs: 80,
  current_directory: '/Documents/Projects',
  current_file: 'important-document.pdf',
  errors: 0,
  warnings: 1,
  is_active: true,
  ...overrides,
});

// Helper function to simulate progress updates
const simulateProgressUpdate = (progressData: SyncProgressInfo) => {
  const mockEventSource = getMockEventSource();
  act(() => {
    const progressHandler = mockEventSource.addEventListener.mock.calls.find(
      call => call[0] === 'progress'
    )?.[1] as (event: MessageEvent) => void;
    
    if (progressHandler) {
      progressHandler(new MessageEvent('progress', {
        data: JSON.stringify(progressData)
      }));
    }
  });
};

const renderComponent = (props: Partial<React.ComponentProps<typeof SyncProgressDisplay>> = {}) => {
  const defaultProps = {
    sourceId: 'test-source-123',
    sourceName: 'Test WebDAV Source',
    isVisible: true,
    ...props,
  };

  return renderWithProviders(<SyncProgressDisplay {...defaultProps} />);
};

describe('SyncProgressDisplay Component', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Reset the mock EventSource instance
    resetMockEventSource();
  });

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

  describe('Visibility and Rendering', () => {
    test('should not render when isVisible is false', () => {
      renderComponent({ isVisible: false });
      expect(screen.queryByText('Test WebDAV Source - Sync Progress')).not.toBeInTheDocument();
    });

    test('should render when isVisible is true', () => {
      renderComponent({ isVisible: true });
      expect(screen.getByText('Test WebDAV Source - Sync Progress')).toBeInTheDocument();
    });

    test('should show connecting status initially', () => {
      renderComponent();
      expect(screen.getByText('Connecting...')).toBeInTheDocument();
    });

    test('should render with custom source name', () => {
      renderComponent({ sourceName: 'My Custom Source' });
      expect(screen.getByText('My Custom Source - Sync Progress')).toBeInTheDocument();
    });
  });

  describe('SSE Connection Management', () => {
    test('should create EventSource with correct URL', async () => {
      renderComponent();
      
      // Since the component creates the stream, we can verify by checking if our mock was called
      // The component should call getSyncProgressStream during mount
      await waitFor(() => {
        // Check that our global EventSource constructor was called with the right URL
        expect(global.EventSource).toHaveBeenCalledWith('/api/sources/test-source-123/sync/progress');
      });
    });

    test('should handle successful connection', async () => {
      renderComponent();
      
      // Simulate successful connection
      const mockEventSource = getMockEventSource();
      act(() => {
        if (mockEventSource.onopen) {
          mockEventSource.onopen(new Event('open'));
        }
      });

      // Should show connected status when there's progress data
      const mockProgress = createMockProgressInfo();
      simulateProgressUpdate(mockProgress);

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

    test('should handle connection error', async () => {
      renderComponent();
      
      const mockEventSource = getMockEventSource();
      act(() => {
        if (mockEventSource.onerror) {
          mockEventSource.onerror(new Event('error'));
        }
      });

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

    test('should close EventSource on unmount', () => {
      const { unmount } = renderComponent();
      unmount();
      expect(getMockEventSource().close).toHaveBeenCalled();
    });

    test('should close EventSource when visibility changes to false', () => {
      const { rerender } = renderComponent({ isVisible: true });
      
      rerender(
        <SyncProgressDisplay
          sourceId="test-source-123"
          sourceName="Test WebDAV Source"
          isVisible={false}
        />
      );

      expect(getMockEventSource().close).toHaveBeenCalled();
    });
  });

  describe('Progress Data Display', () => {

    test('should display progress information correctly', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo();
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('Downloading and processing files')).toBeInTheDocument();
        expect(screen.getByText('30 / 50 files (60.0%)')).toBeInTheDocument();
        expect(screen.getByText('7 / 10')).toBeInTheDocument(); // Directories
        expect(screen.getByText('1000 KB')).toBeInTheDocument(); // Bytes processed
        expect(screen.getByText('2.5 files/sec')).toBeInTheDocument(); // Processing rate
        expect(screen.getByText('2m 0s')).toBeInTheDocument(); // Elapsed time
      });
    });

    test('should display current directory and file', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        current_directory: '/Documents/Important',
        current_file: 'presentation.pptx',
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('/Documents/Important')).toBeInTheDocument();
        expect(screen.getByText('presentation.pptx')).toBeInTheDocument();
      });
    });

    test('should display estimated time remaining', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        estimated_time_remaining_secs: 300, // 5 minutes
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText(/Estimated time remaining: 5m 0s/)).toBeInTheDocument();
      });
    });

    test('should display errors and warnings', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        errors: 3,
        warnings: 5,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('3 errors')).toBeInTheDocument();
        expect(screen.getByText('5 warnings')).toBeInTheDocument();
      });
    });

    test('should handle singular error/warning labels', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        errors: 1,
        warnings: 1,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('1 error')).toBeInTheDocument();
        expect(screen.getByText('1 warning')).toBeInTheDocument();
      });
    });

    test('should not show errors/warnings when count is zero', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        errors: 0,
        warnings: 0,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.queryByText(/error/)).not.toBeInTheDocument();
        expect(screen.queryByText(/warning/)).not.toBeInTheDocument();
      });
    });

    test('should not show estimated time when not available', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        estimated_time_remaining_secs: undefined,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.queryByText(/Estimated time remaining/)).not.toBeInTheDocument();
      });
    });
  });

  describe('Phase Indicators and Colors', () => {
    const testPhases = [
      { phase: 'initializing', color: 'info', description: 'Initializing sync operation' },
      { phase: 'discovering_files', color: 'warning', description: 'Discovering files to sync' },
      { phase: 'processing_files', color: 'primary', description: 'Downloading and processing files' },
      { phase: 'completed', color: 'success', description: 'Sync completed successfully' },
      { phase: 'failed', color: 'error', description: 'Sync failed: Connection timeout' },
    ];

    testPhases.forEach(({ phase, description }) => {
      test(`should display correct phase description for ${phase}`, async () => {
        renderComponent();
        const mockProgress = createMockProgressInfo({
          phase,
          phase_description: description,
        });
        
        simulateProgressUpdate(mockProgress);

        await waitFor(() => {
          expect(screen.getByText(description)).toBeInTheDocument();
        });
      });
    });
  });

  describe('Progress Bar', () => {
    test('should show progress bar when files are found', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        files_found: 100,
        files_processed: 75,
        files_progress_percent: 75.0,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        const progressBar = screen.getByRole('progressbar');
        expect(progressBar).toBeInTheDocument();
        expect(progressBar).toHaveAttribute('aria-valuenow', '75');
      });
    });

    test('should not show progress bar when no files found', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        files_found: 0,
        files_processed: 0,
      });
      
      simulateProgressUpdate(mockProgress);

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

  describe('Expand/Collapse Functionality', () => {
    test('should be expanded by default', () => {
      renderComponent();
      expect(screen.getByText('Waiting for sync progress information...')).toBeInTheDocument();
    });

    test('should collapse when collapse button is clicked', async () => {
      renderComponent();
      
      const collapseButton = screen.getByLabelText('Collapse');
      fireEvent.click(collapseButton);

      await waitFor(() => {
        // After clicking collapse, the button should change to expand
        expect(screen.getByLabelText('Expand')).toBeInTheDocument();
        // The content is still in DOM but hidden by Material-UI Collapse
        const collapseElement = screen.getByText('Waiting for sync progress information...').closest('.MuiCollapse-root');
        expect(collapseElement).toHaveClass('MuiCollapse-hidden');
      });
    });

    test('should expand when expand button is clicked', async () => {
      renderComponent();
      
      // First collapse
      const collapseButton = screen.getByLabelText('Collapse');
      fireEvent.click(collapseButton);

      await waitFor(() => {
        expect(screen.getByLabelText('Expand')).toBeInTheDocument();
        const collapseElement = screen.getByText('Waiting for sync progress information...').closest('.MuiCollapse-root');
        expect(collapseElement).toHaveClass('MuiCollapse-hidden');
      });

      // Then expand
      const expandButton = screen.getByLabelText('Expand');
      fireEvent.click(expandButton);

      await waitFor(() => {
        expect(screen.getByLabelText('Collapse')).toBeInTheDocument();
        const collapseElement = screen.getByText('Waiting for sync progress information...').closest('.MuiCollapse-root');
        expect(collapseElement).toHaveClass('MuiCollapse-entered');
      });
    });
  });

  describe('Data Formatting', () => {
    test('should format bytes correctly', async () => {
      renderComponent();
      
      // Test 1.0 KB case
      const mockProgress1KB = createMockProgressInfo({ bytes_processed: 1024 });
      simulateProgressUpdate(mockProgress1KB);

      await waitFor(() => {
        expect(screen.getByText('1 KB')).toBeInTheDocument();
      });
    });

    test('should format zero bytes correctly', async () => {
      renderComponent();
      
      // Test 0 B case
      const mockProgress0 = createMockProgressInfo({ bytes_processed: 0 });
      simulateProgressUpdate(mockProgress0);

      await waitFor(() => {
        expect(screen.getByText('0 B')).toBeInTheDocument();
      });
    });

    test('should format duration correctly', async () => {
      renderComponent();
      
      const testCases = [
        { seconds: 30, expected: '30s' },
        { seconds: 90, expected: '1m 30s' },
        { seconds: 3661, expected: '1h 1m' },
      ];

      for (const { seconds, expected } of testCases) {
        const mockProgress = createMockProgressInfo({ elapsed_time_secs: seconds });
        simulateProgressUpdate(mockProgress);

        await waitFor(() => {
          expect(screen.getByText(expected)).toBeInTheDocument();
        });
      }
    });
  });

  describe('Heartbeat Handling', () => {
    test('should clear progress info on inactive heartbeat', async () => {
      renderComponent();
      
      // First set some progress
      const mockProgress = createMockProgressInfo();
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('Downloading and processing files')).toBeInTheDocument();
      });

      // Then send inactive heartbeat
      const mockEventSource = getMockEventSource();
      act(() => {
        const heartbeatHandler = mockEventSource.addEventListener.mock.calls.find(
          call => call[0] === 'heartbeat'
        )?.[1] as (event: MessageEvent) => void;
        
        if (heartbeatHandler) {
          heartbeatHandler(new MessageEvent('heartbeat', {
            data: JSON.stringify({
              source_id: 'test-source-123',
              is_active: false,
              timestamp: Date.now()
            })
          }));
        }
      });

      await waitFor(() => {
        expect(screen.getByText('Waiting for sync progress information...')).toBeInTheDocument();
      });
    });
  });

  describe('Error Handling', () => {
    test('should handle malformed progress data gracefully', async () => {
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
      
      renderComponent();
      
      const mockEventSource = getMockEventSource();
      act(() => {
        const progressHandler = mockEventSource.addEventListener.mock.calls.find(
          call => call[0] === 'progress'
        )?.[1] as (event: MessageEvent) => void;
        
        if (progressHandler) {
          progressHandler(new MessageEvent('progress', {
            data: 'invalid json'
          }));
        }
      });

      await waitFor(() => {
        expect(consoleSpy).toHaveBeenCalledWith('Failed to parse progress event:', expect.any(Error));
      });

      consoleSpy.mockRestore();
    });

    test('should handle malformed heartbeat data gracefully', async () => {
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
      
      renderComponent();
      
      const mockEventSource = getMockEventSource();
      act(() => {
        const heartbeatHandler = mockEventSource.addEventListener.mock.calls.find(
          call => call[0] === 'heartbeat'
        )?.[1] as (event: MessageEvent) => void;
        
        if (heartbeatHandler) {
          heartbeatHandler(new MessageEvent('heartbeat', {
            data: 'invalid json'
          }));
        }
      });

      await waitFor(() => {
        expect(consoleSpy).toHaveBeenCalledWith('Failed to parse heartbeat event:', expect.any(Error));
      });

      consoleSpy.mockRestore();
    });
  });

  describe('Edge Cases', () => {
    test('should handle missing current_file gracefully', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        current_directory: '/Documents',
        current_file: undefined,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('/Documents')).toBeInTheDocument();
        expect(screen.queryByText('Current File')).not.toBeInTheDocument();
      });
    });

    test('should handle zero processing rate', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        processing_rate_files_per_sec: 0.0,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('0.0 files/sec')).toBeInTheDocument();
      });
    });

    test('should handle very large numbers', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo({
        bytes_processed: 1099511627776, // 1 TB
        files_found: 999999,
        files_processed: 500000,
      });
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        expect(screen.getByText('1 TB')).toBeInTheDocument();
        // Check for the large file numbers - they might be split across multiple elements
        expect(screen.getByText(/500000/)).toBeInTheDocument();
        expect(screen.getByText(/999999/)).toBeInTheDocument();
      });
    });
  });

  describe('Accessibility', () => {
    test('should have proper ARIA labels', () => {
      renderComponent();
      
      expect(screen.getByLabelText('Collapse')).toBeInTheDocument();
    });

    test('should have accessible progress bar', async () => {
      renderComponent();
      const mockProgress = createMockProgressInfo();
      
      simulateProgressUpdate(mockProgress);

      await waitFor(() => {
        const progressBar = screen.getByRole('progressbar');
        expect(progressBar).toHaveAttribute('aria-valuenow', '60');
        expect(progressBar).toHaveAttribute('aria-valuemin', '0');
        expect(progressBar).toHaveAttribute('aria-valuemax', '100');
      });
    });
  });
});