// src/features/flyer/FlyerUploader.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
import { FlyerUploader } from './FlyerUploader';
import * as aiApiClientModule from '../../services/aiApiClient';
import * as checksumModule from '../../utils/checksum';
import { useNavigate } from 'react-router-dom';

// Mock dependencies
vi.mock('../../services/aiApiClient');
vi.mock('../../services/logger.client', () => ({
  logger: { info: vi.fn(), error: vi.fn() },
}));
vi.mock('../../utils/checksum', () => ({
  generateFileChecksum: vi.fn(),
}));

// Mock react-router-dom
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
  return {
    ...actual,
    useNavigate: vi.fn(),
  };
});

const mockedAiApiClient = aiApiClientModule as unknown as {
  uploadAndProcessFlyer: Mock;
  getJobStatus: Mock;
};
const mockedChecksumModule = checksumModule as unknown as {
  generateFileChecksum: Mock;
};

const renderComponent = (onProcessingComplete = vi.fn()) => {
  // We use a real MemoryRouter for the Link components, but we spy on useNavigate
  const { MemoryRouter } = require('react-router-dom');
  return render(
    <MemoryRouter>
      <FlyerUploader onProcessingComplete={onProcessingComplete} />
    </MemoryRouter>
  );
};

describe('FlyerUploader', () => {
  const navigateSpy = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers(); // Use fake timers for ALL tests in this suite
    
    mockedChecksumModule.generateFileChecksum.mockResolvedValue('mock-checksum');
    (useNavigate as Mock).mockReturnValue(navigateSpy);
  });

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

  it('should render the initial state correctly', () => {
    renderComponent();
    expect(screen.getByText('Upload New Flyer')).toBeInTheDocument();
    expect(screen.getByText(/click to select a file/i)).toBeInTheDocument();
  });

  it('should handle file upload and start polling', async () => {
    // 1. Setup Mocks
    mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue(
      new Response(JSON.stringify({ jobId: 'job-123' }), { status: 200 })
    );
    // The component polls immediately upon upload success. 
    // We mock it to return 'active' so it enters the polling loop.
    mockedAiApiClient.getJobStatus.mockResolvedValue(
      new Response(JSON.stringify({ state: 'active', progress: { message: 'Checking...' } }))
    );

    // 2. Render and Interaction
    renderComponent();
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to select a file/i);

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

    // 3. Assertions
    // Wait for the upload call
    await waitFor(() => {
      expect(mockedAiApiClient.uploadAndProcessFlyer).toHaveBeenCalledWith(file, 'mock-checksum');
    });

    // Wait for the UI to switch to the polling/status view after the FIRST poll
    await screen.findByText('Checking...'); 
    
    // Verify the immediate getJobStatus was called
    expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);

    // --- ADD THIS SECTION TO PREVENT TIMEOUT ---
    // Now, explicitly advance the timer to trigger the next polling cycle.
    // This ensures the test doesn't hang with a pending timer.
    await act(async () => {
      // The component waits 3000ms, we advance by that much.
      await vi.advanceTimersByTimeAsync(3000);
    });

    // After advancing the timer, the second poll should have been made.
    await waitFor(() => {
      expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
    });
  });

  it('should poll for status, complete successfully, and redirect', async () => {
    const onProcessingComplete = vi.fn();

    // 1. Setup Mocks sequence
    mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue(
      new Response(JSON.stringify({ jobId: 'job-123' }), { status: 200 })
    );

    // Sequence: 
    // Call 1 (Immediate): 'active' -> sets timeout for 3s
    // Call 2 (After timeout): 'completed' -> triggers redirect
    mockedAiApiClient.getJobStatus
      .mockResolvedValueOnce(new Response(JSON.stringify({ state: 'active', progress: { message: 'Analyzing...' } })))
      .mockResolvedValueOnce(new Response(JSON.stringify({ state: 'completed', returnValue: { flyerId: 42 } })));

    // 2. Render & Upload
    renderComponent(onProcessingComplete);
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to select a file/i);

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

    // 3. Verify Initial Poll (Active)
    // This waits for the immediate poll to finish and update UI
    await screen.findByText('Analyzing...'); 
    expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(1);

    // 4. Fast-forward time to trigger the next poll
    // The component waits 3000ms. We advance slightly more to be safe.
    // 'advanceTimersByTimeAsync' is crucial here as it flushes the promise microtasks.
    await act(async () => {
      await vi.advanceTimersByTimeAsync(4000);
    });

    // 5. Verify Second Poll (Completed)
    await waitFor(() => {
      expect(mockedAiApiClient.getJobStatus).toHaveBeenCalledTimes(2);
      expect(screen.getByText('Processing complete! Redirecting to flyer 42...')).toBeInTheDocument();
    });

    // 6. Verify Redirect
    // The component has a 1500ms delay before redirecting.
    await act(async () => {
      await vi.advanceTimersByTimeAsync(2000);
    });

    expect(onProcessingComplete).toHaveBeenCalled();
    expect(navigateSpy).toHaveBeenCalledWith('/flyers/42');
  });

  it('should handle a failed job', async () => {
    // 1. Setup Mocks
    mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue(
      new Response(JSON.stringify({ jobId: 'job-fail' }), { status: 200 })
    );
    // Immediate failure on first poll
    mockedAiApiClient.getJobStatus.mockResolvedValue(
      new Response(JSON.stringify({ state: 'failed', failedReason: 'AI model exploded' }))
    );

    // 2. Render & Upload
    renderComponent();
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to select a file/i);

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

    // 3. Verify Failure State
    await waitFor(() => {
        expect(screen.getByText(/Processing failed: AI model exploded/i)).toBeInTheDocument();
    });
    // Should verify "Upload Another Flyer" button is present
    expect(screen.getByText('Upload Another Flyer')).toBeInTheDocument();
  });

  it('should handle a duplicate flyer error (409)', async () => {
    mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue(
      new Response(JSON.stringify({ flyerId: 99, message: 'Duplicate' }), { status: 409 })
    );

    renderComponent();
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to select a file/i);

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

    await waitFor(() => {
      expect(screen.getByText('This flyer has already been processed. You can view it here:')).toBeInTheDocument();
      // We look for the link specifically
      const link = screen.getByRole('link', { name: /Flyer #99/i });
      expect(link).toHaveAttribute('href', '/flyers/99');
    });
  });

  it('should allow the user to stop watching progress', async () => {
    // 1. Setup Mocks: Infinite active state
    mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue(
      new Response(JSON.stringify({ jobId: 'job-stop' }), { status: 200 })
    );
    mockedAiApiClient.getJobStatus.mockResolvedValue(
      new Response(JSON.stringify({ state: 'active', progress: { message: 'Analyzing...' } }))
    );

    // 2. Render & Upload
    renderComponent();
    const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
    const input = screen.getByLabelText(/click to select a file/i);

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

    // 3. Wait for UI to show Polling state
    await screen.findByText('Analyzing...');

    // 4. Find and Click Stop Button
    const stopButton = screen.getByRole('button', { name: 'Stop Watching Progress' });
    await act(async () => {
      fireEvent.click(stopButton);
    });

    // 5. Verify Reset to Idle
    await waitFor(() => {
      expect(screen.getByText(/click to select a file/i)).toBeInTheDocument();
      expect(screen.queryByText('Analyzing...')).not.toBeInTheDocument();
    });
  });
});