// src/pages/admin/components/CorrectionRow.test.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import { CorrectionRow } from './CorrectionRow';
import * as apiClient from '../../../services/apiClient';
import type { SuggestedCorrection, MasterGroceryItem, Category } from '../../../types';

// Cast the mocked module to its mocked type to retain type safety and autocompletion.
// The apiClient is now mocked globally via src/tests/setup/tests-setup-unit.ts.
const mockedApiClient = apiClient as Mocked<typeof apiClient>;

// Mock the logger
vi.mock('../../../services/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn() },
}));

// Mock the ConfirmationModal to test its props and interactions
// The ConfirmationModal is now in a different directory.
// We use ReactDOM.createPortal to avoid rendering a <div> inside a <tbody>, which is invalid HTML.
vi.mock('../../../components/ConfirmationModal', () => ({
  ConfirmationModal: vi.fn(
    ({ isOpen, onConfirm, onClose }: { isOpen: boolean; onConfirm: () => void; onClose: () => void }) => {
      if (!isOpen) return null;
      // Use a portal to render the modal at the body level, avoiding invalid nesting.
      return ReactDOM.createPortal(
        <div data-testid="confirmation-modal">
          <button onClick={onConfirm}>Confirm</button>
          <button onClick={onClose}>Cancel</button>
        </div>,
        document.body
      );
    }
  ),
}));

const mockCorrection: SuggestedCorrection = {
  suggested_correction_id: 1,
  flyer_item_id: 101,
  user_id: 'user-1',
  correction_type: 'WRONG_PRICE',
  suggested_value: '250', // $2.50
  status: 'pending',
  created_at: new Date().toISOString(),
  flyer_item_name: 'Bananas',
  flyer_item_price_display: '$1.99',
  user_email: 'test@example.com',
};

const mockMasterItems: MasterGroceryItem[] = [
  { master_grocery_item_id: 1, name: 'Bananas', created_at: '', category_id: 1, category_name: 'Produce' },
];

const mockCategories: Category[] = [
  { category_id: 1, name: 'Produce' },
];

const mockOnProcessed = vi.fn();

const defaultProps = {
  correction: mockCorrection,
  masterItems: mockMasterItems,
  categories: mockCategories,
  onProcessed: mockOnProcessed,
};

// Helper to render the component inside a table structure
const renderInTable = (props = defaultProps) => {
  return render(
    <table>
      <tbody>
        <CorrectionRow {...props} />
      </tbody>
    </table>
  );
};

describe('CorrectionRow', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockedApiClient.approveCorrection.mockResolvedValue(new Response(null, { status: 204 }));
    mockedApiClient.rejectCorrection.mockResolvedValue(new Response(null, { status: 204 }));
    mockedApiClient.updateSuggestedCorrection.mockResolvedValue(new Response(JSON.stringify({ ...mockCorrection, suggested_value: '300' })));
  });

  it('should render correction data correctly', () => {
    renderInTable();
    expect(screen.getByText('Bananas')).toBeInTheDocument();
    expect(screen.getByText('Original Price: $1.99')).toBeInTheDocument();
    expect(screen.getByText('WRONG_PRICE')).toBeInTheDocument();
    expect(screen.getByText('$2.50')).toBeInTheDocument(); // Formatted price
    expect(screen.getByText('test@example.com')).toBeInTheDocument();
  });

  it('should open the confirmation modal on approve click', async () => {
    renderInTable();
    fireEvent.click(screen.getByTitle('Approve'));
    await waitFor(() => {
      expect(screen.getByTestId('confirmation-modal')).toBeInTheDocument();
    });
  });

  it('should call approveCorrection when approval is confirmed', async () => {
    renderInTable();
    fireEvent.click(screen.getByTitle('Approve'));
    await waitFor(() => {
      fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
    });
    await waitFor(() => {
      expect(mockedApiClient.approveCorrection).toHaveBeenCalledWith(mockCorrection.suggested_correction_id);
      expect(mockOnProcessed).toHaveBeenCalledWith(mockCorrection.suggested_correction_id);
    });
  });

  it('should call rejectCorrection when rejection is confirmed', async () => {
    renderInTable();
    fireEvent.click(screen.getByTitle('Reject'));
    await waitFor(() => {
      expect(screen.getByTestId('confirmation-modal')).toBeInTheDocument();
      fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
    });
    await waitFor(() => {
      expect(mockedApiClient.rejectCorrection).toHaveBeenCalledWith(mockCorrection.suggested_correction_id);
      expect(mockOnProcessed).toHaveBeenCalledWith(mockCorrection.suggested_correction_id);
    });
  });

  it('should display an error message if an action fails', async () => {
    mockedApiClient.approveCorrection.mockRejectedValue(new Error('API Error'));
    renderInTable();
    fireEvent.click(screen.getByTitle('Approve'));
    await waitFor(() => {
      fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
    });
    await waitFor(() => {
      expect(screen.getByText('API Error')).toBeInTheDocument();
    });
  });

  it('should enter and exit editing mode', async () => {
    renderInTable();
    // Enter editing mode
    fireEvent.click(screen.getByTitle('Edit'));
    await waitFor(() => {
      expect(screen.getByRole('spinbutton')).toBeInTheDocument(); // For input type=number
      expect(screen.getByTitle('Save')).toBeInTheDocument();
      expect(screen.getByTitle('Cancel')).toBeInTheDocument();
    });

    // Exit editing mode
    fireEvent.click(screen.getByTitle('Cancel'));
    await waitFor(() => {
      expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument();
      expect(screen.getByTitle('Approve')).toBeInTheDocument();
    });
  });

  it('should save an edited value', async () => {
    renderInTable();
    fireEvent.click(screen.getByTitle('Edit'));

    const input = await screen.findByRole('spinbutton');
    fireEvent.change(input, { target: { value: '300' } });
    fireEvent.click(screen.getByTitle('Save'));

    await waitFor(() => {
      expect(mockedApiClient.updateSuggestedCorrection).toHaveBeenCalledWith(mockCorrection.suggested_correction_id, '300');
      // The component should now display the updated value from the mock response
      expect(screen.getByText('$3.00')).toBeInTheDocument();
    });

    // Check that it exited editing mode
    expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument();
  });
});