// src/pages/admin/CorrectionsPage.test.tsx
import React from 'react';
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { CorrectionsPage } from './CorrectionsPage';
import * as apiClient from '../../services/apiClient';
import type { SuggestedCorrection, MasterGroceryItem, Category } from '../../types';
import {
  createMockSuggestedCorrection,
  createMockMasterGroceryItem,
  createMockCategory,
} from '../../tests/utils/mockFactories';

// The apiClient and logger are now mocked globally via src/tests/setup/tests-setup-unit.ts.
const mockedApiClient = vi.mocked(apiClient);

// Mock the child CorrectionRow component to isolate the test to the page itself
// The CorrectionRow component is now located in a sub-directory.
vi.mock('./components/CorrectionRow', async () => {
  const { MockCorrectionRow } = await import('../../tests/utils/componentMocks');
  return { CorrectionRow: MockCorrectionRow };
});

// Helper to render the component within a router context
const renderWithRouter = () => {
  return render(
    <MemoryRouter>
      <CorrectionsPage />
    </MemoryRouter>,
  );
};

describe('CorrectionsPage', () => {
  const mockCorrections: SuggestedCorrection[] = [
    createMockSuggestedCorrection({
      suggested_correction_id: 1,
      flyer_item_id: 101,
      user_id: 'user-1',
      correction_type: 'item_name',
      suggested_value: 'Organic Bananas',
      flyer_item_name: 'Bananas',
      user_email: 'test@example.com',
    }),
    createMockSuggestedCorrection({
      suggested_correction_id: 2,
      flyer_item_id: 102,
      user_id: 'user-2',
      correction_type: 'price_in_cents',
      suggested_value: '199',
      flyer_item_name: 'Apples',
      user_email: 'test2@example.com',
    }),
  ];
  const mockMasterItems: MasterGroceryItem[] = [
    createMockMasterGroceryItem({
      master_grocery_item_id: 1,
      name: 'Organic Bananas',
      category_id: 1,
      category_name: 'Produce',
    }),
  ];
  const mockCategories: Category[] = [createMockCategory({ category_id: 1, name: 'Produce' })];

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

  it('should render a loading spinner while fetching data', async () => {
    let resolvePromise: (value: Response) => void;
    const mockPromise = new Promise<Response>((resolve) => {
      resolvePromise = resolve;
    });
    // Cast to any to bypass strict type checking for the mock return value vs Promise
    mockedApiClient.getSuggestedCorrections.mockReturnValue(mockPromise as any);
    // Mock other calls to resolve immediately so Promise.all waits on the one we control
    mockedApiClient.fetchMasterItems.mockResolvedValue(new Response(JSON.stringify([])));
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify([])));

    renderWithRouter();

    expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();

    await act(async () => {
      resolvePromise!(new Response(JSON.stringify([])));
    });
  });

  it('should display corrections when data is fetched successfully', async () => {
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(
      new Response(JSON.stringify(mockCorrections)),
    );
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));
    renderWithRouter();

    await waitFor(() => {
      // Check for the mocked CorrectionRow components
      expect(screen.getByTestId('correction-row-1')).toBeInTheDocument(); // This will now use suggested_correction_id
      expect(screen.getByTestId('correction-row-2')).toBeInTheDocument(); // This will now use suggested_correction_id
      // Check for the text content within the mocked rows
      expect(screen.getByText('Bananas')).toBeInTheDocument();
      expect(screen.getByText('Apples')).toBeInTheDocument();
    });
  });

  it('should display a message when there are no pending corrections', async () => {
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(new Response(JSON.stringify([])));
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));
    renderWithRouter();

    await waitFor(() => {
      expect(screen.getByText(/no pending corrections. great job!/i)).toBeInTheDocument();
    });
  });

  it('should display an error message if fetching corrections fails', async () => {
    const errorMessage = 'Network Error: Failed to fetch';
    mockedApiClient.getSuggestedCorrections.mockRejectedValue(new Error(errorMessage));
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));
    renderWithRouter();

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

  it('should display an error message if fetching master items fails', async () => {
    const errorMessage = 'Could not retrieve master items list.';
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(
      new Response(JSON.stringify(mockCorrections)),
    );
    mockedApiClient.fetchMasterItems.mockRejectedValue(new Error(errorMessage));
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));
    renderWithRouter();

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

  it('should display an error message if fetching categories fails', async () => {
    const errorMessage = 'Could not retrieve categories.';
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(
      new Response(JSON.stringify(mockCorrections)),
    );
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockRejectedValue(new Error(errorMessage));
    renderWithRouter();

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

  it('should handle unknown errors gracefully', async () => {
    mockedApiClient.getSuggestedCorrections.mockRejectedValue('Unknown string error');
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));
    renderWithRouter();

    await waitFor(() => {
      expect(
        screen.getByText('An unknown error occurred while fetching corrections.'),
      ).toBeInTheDocument();
    });
  });

  it('should refresh corrections when the refresh button is clicked', async () => {
    // Mock the initial data load
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(
      new Response(JSON.stringify(mockCorrections)),
    );
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));

    renderWithRouter();
    // Wait for the initial data to be rendered
    await waitFor(() => expect(screen.getByText('Bananas')).toBeInTheDocument());

    // All APIs should have been called once on initial load
    expect(mockedApiClient.getSuggestedCorrections).toHaveBeenCalledTimes(1);
    expect(mockedApiClient.fetchMasterItems).toHaveBeenCalledTimes(1);
    expect(mockedApiClient.fetchCategories).toHaveBeenCalledTimes(1);

    // Click refresh
    const refreshButton = screen.getByTitle('Refresh Corrections');
    fireEvent.click(refreshButton);

    // Wait for the APIs to be called a second time
    await waitFor(() => expect(mockedApiClient.getSuggestedCorrections).toHaveBeenCalledTimes(2));
    expect(mockedApiClient.fetchMasterItems).toHaveBeenCalledTimes(2);
    expect(mockedApiClient.fetchCategories).toHaveBeenCalledTimes(2);
  });

  it('should remove a correction from the list when processed', async () => {
    mockedApiClient.getSuggestedCorrections.mockResolvedValue(
      new Response(JSON.stringify(mockCorrections)),
    );
    mockedApiClient.fetchMasterItems.mockResolvedValue(
      new Response(JSON.stringify(mockMasterItems)),
    );
    mockedApiClient.fetchCategories.mockResolvedValue(new Response(JSON.stringify(mockCategories)));

    renderWithRouter();
    await waitFor(() => expect(screen.getByTestId('correction-row-1')).toBeInTheDocument());

    // Click the process button in the mock row for ID 1
    fireEvent.click(screen.getByTestId('process-btn-1'));

    // It should disappear
    await waitFor(() => expect(screen.queryByTestId('correction-row-1')).not.toBeInTheDocument());
    expect(screen.getByTestId('correction-row-2')).toBeInTheDocument();
  });
});
