// src/features/flyer/ExtractedDataTable.test.tsx
import React from 'react';
import { render, screen, fireEvent, within, prettyDOM } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ExtractedDataTable, ExtractedDataTableProps } from './ExtractedDataTable';
import type { FlyerItem, MasterGroceryItem, ShoppingList } from '../../types';
import { useAuth } from '../../hooks/useAuth';
import {
  createMockFlyerItem,
  createMockMasterGroceryItem,
  createMockShoppingList,
  createMockShoppingListItem,
  createMockUser,
  createMockUserProfile,
} from '../../tests/utils/mockFactories';
import { useUserData } from '../../hooks/useUserData';
import { useMasterItems } from '../../hooks/useMasterItems';
import { useWatchedItems } from '../../hooks/useWatchedItems';
import { useShoppingLists } from '../../hooks/useShoppingLists';

// Mock all the data hooks
vi.mock('../../hooks/useAuth');
vi.mock('../../hooks/useUserData');
vi.mock('../../hooks/useMasterItems');
vi.mock('../../hooks/useWatchedItems');
vi.mock('../../hooks/useShoppingLists');

const mockUser = createMockUser({ user_id: 'user-123', email: 'test@example.com' });
const mockUserProfile = createMockUserProfile({ user: mockUser });

const mockMasterItems: MasterGroceryItem[] = [
  createMockMasterGroceryItem({
    master_grocery_item_id: 1,
    name: 'Apples',
    category_id: 1,
    category_name: 'Produce',
  }),
  createMockMasterGroceryItem({
    master_grocery_item_id: 2,
    name: 'Milk',
    category_id: 2,
    category_name: 'Dairy',
  }),
  createMockMasterGroceryItem({
    master_grocery_item_id: 3,
    name: 'Chicken Breast',
    category_id: 3,
    category_name: 'Meat',
  }),
];

const mockFlyerItems: FlyerItem[] = [
  createMockFlyerItem({
    flyer_item_id: 101,
    item: 'Gala Apples',
    price_display: '$1.99/lb',
    price_in_cents: 199,
    quantity: 'per lb',
    unit_price: { value: 1.99, unit: 'lb' },
    master_item_id: 1,
    category_name: 'Produce',
    flyer_id: 1,
  }),
  createMockFlyerItem({
    flyer_item_id: 102,
    item: '2% Milk',
    price_display: '$4.50',
    price_in_cents: 450,
    quantity: '4L',
    unit_price: { value: 1.125, unit: 'L' },
    master_item_id: 2,
    category_name: 'Dairy',
    flyer_id: 1,
  }),
  createMockFlyerItem({
    flyer_item_id: 103,
    item: 'Boneless Chicken',
    price_display: '$8.00/kg',
    price_in_cents: 800,
    quantity: 'per kg',
    unit_price: { value: 8.0, unit: 'kg' },
    master_item_id: 3,
    category_name: 'Meat',
    flyer_id: 1,
  }),
  createMockFlyerItem({
    flyer_item_id: 104,
    item: 'Mystery Soda',
    price_display: '$1.00',
    price_in_cents: 100,
    quantity: '1 can',
    unit_price: { value: 1.0, unit: 'can' },
    master_item_id: undefined,
    category_name: 'Beverages',
    flyer_id: 1,
  }), // Unmatched item
  createMockFlyerItem({
    flyer_item_id: 105,
    item: 'Apples',
    price_display: '$2.50/lb',
    price_in_cents: 250,
    quantity: 'per lb',
    unit_price: { value: 2.5, unit: 'lb' },
    master_item_id: 1,
    category_name: 'Produce',
    flyer_id: 1,
  }), // Item name matches canonical name
];

const mockShoppingLists: ShoppingList[] = [
  createMockShoppingList({
    shopping_list_id: 1,
    name: 'My List',
    user_id: 'user-123',
    items: [
      createMockShoppingListItem({
        shopping_list_item_id: 1,
        shopping_list_id: 1,
        master_item_id: 2,
        quantity: 1,
        is_purchased: false,
      }),
    ], // Contains Milk
  }),
];

const mockAddWatchedItem = vi.fn();
const mockAddItemToList = vi.fn();

const defaultProps: ExtractedDataTableProps = {
  items: mockFlyerItems,
  unitSystem: 'imperial' as const,
  // The following props are now sourced from the mocked hooks below,
  // so they are not needed here and have been removed from the component's signature.
  // totalActiveItems: mockFlyerItems.length,
  // watchedItems: [],
  // masterItems: mockMasterItems,
  // user: mockUser,
  // onAddItem: mockAddWatchedItem,
  // shoppingLists: mockShoppingLists,
  // activeListId: 1,
  // onAddItemToList: mockAddItemToList,
};

describe('ExtractedDataTable', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Provide default mocks for all hooks
    vi.mocked(useAuth).mockReturnValue({
      userProfile: mockUserProfile,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    vi.mocked(useUserData).mockReturnValue({
      watchedItems: [],
      shoppingLists: mockShoppingLists,
      isLoading: false,
      error: null,
    });
    vi.mocked(useMasterItems).mockReturnValue({
      masterItems: mockMasterItems,
      isLoading: false,
      error: null,
    });
    vi.mocked(useWatchedItems).mockReturnValue({
      addWatchedItem: mockAddWatchedItem,
      watchedItems: [],
      removeWatchedItem: vi.fn(),
      error: null,
    });
    vi.mocked(useShoppingLists).mockReturnValue({
      activeListId: 1,
      addItemToList: mockAddItemToList,
      shoppingLists: mockShoppingLists, // Add this to satisfy the component's dependency
      setActiveListId: vi.fn(),
      createList: vi.fn(),
      deleteList: vi.fn(),
      updateItemInList: vi.fn(),
      removeItemFromList: vi.fn(),
      isCreatingList: false,
      isDeletingList: false,
      isAddingItem: false,
      isUpdatingItem: false,
      isRemovingItem: false,
      error: null,
    });
  });

  it('should render an empty state message if no items are provided', () => {
    render(<ExtractedDataTable items={[]} unitSystem="imperial" />);
    expect(screen.getByText('No items extracted yet.')).toBeInTheDocument();
  });

  it('should render the table with items', () => {
    render(<ExtractedDataTable {...defaultProps} />);
    expect(screen.getByRole('heading', { name: /item list/i })).toBeInTheDocument();
    expect(screen.getByText('Gala Apples')).toBeInTheDocument();
    expect(screen.getByText('2% Milk')).toBeInTheDocument();
  });

  it('should not show watch/add to list buttons for anonymous users', () => {
    vi.mocked(useAuth).mockReturnValue({
      userProfile: null,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    render(<ExtractedDataTable {...defaultProps} />);
    expect(screen.queryByRole('button', { name: /watch/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /add to list/i })).not.toBeInTheDocument();
  });

  describe('Item States and Interactions', () => {
    it('should highlight watched items and hide the watch button', () => {
      vi.mocked(useUserData).mockReturnValue({
        watchedItems: [mockMasterItems[0]], // 'Apples' is watched
        shoppingLists: [],
        isLoading: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);
      const appleItemRow = screen.getByText('Gala Apples').closest('tr');
      expect(appleItemRow).toHaveTextContent('Gala Apples');
      expect(appleItemRow!.querySelector('.font-bold')).toBeInTheDocument(); // Watched items are bold
      expect(appleItemRow!.querySelector('button[title*="Watch"]')).not.toBeInTheDocument();
    });

    it('should show the watch button for unwatched, matched items', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      // Find the specific table row for the item first to make the test more specific.
      const chickenItemRow = screen.getByText('Boneless Chicken').closest('tr')!;
      // Now, find the watch button *within* that row.
      const watchButton = within(chickenItemRow).getByTitle(
        "Add 'Chicken Breast' to your watchlist",
      );
      expect(watchButton).toBeInTheDocument();

      fireEvent.click(watchButton);
      expect(mockAddWatchedItem).toHaveBeenCalledWith('Chicken Breast', 'Meat');
    });

    it('should not show watch or add to list buttons for unmatched items', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      const sodaItemRow = screen.getByText('Mystery Soda').closest('tr');
      expect(sodaItemRow!.querySelector('button[title*="Watch"]')).not.toBeInTheDocument();
      expect(sodaItemRow!.querySelector('button[title*="list"]')).not.toBeInTheDocument();
    });

    it('should hide the add to list button for items already in the active list', () => {
      // 'Milk' (master_item_id: 2) is in the active shopping list
      vi.mocked(useShoppingLists).mockReturnValue({
        activeListId: 1,
        addItemToList: mockAddItemToList,
        shoppingLists: mockShoppingLists, // 'Milk' is in this list
        setActiveListId: vi.fn(),
        createList: vi.fn(),
        deleteList: vi.fn(),
        updateItemInList: vi.fn(),
        removeItemFromList: vi.fn(),
        isCreatingList: false,
        isDeletingList: false,
        isAddingItem: false,
        isUpdatingItem: false,
        isRemovingItem: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);
      const milkItemRow = screen.getByText('2% Milk').closest('tr')!;
      // Use a more specific query to target the "shopping list" button and not the "watchlist" button.
      const addToListButton = within(milkItemRow).queryByTitle(/add milk to list/i);
      expect(addToListButton).not.toBeInTheDocument();
    });

    it('should show the add to list button for items not in the list', () => {
      vi.mocked(useShoppingLists).mockReturnValue({
        activeListId: 1,
        addItemToList: mockAddItemToList,
        shoppingLists: [{ ...mockShoppingLists[0], items: [] }], // Empty list
        setActiveListId: vi.fn(),
        createList: vi.fn(),
        deleteList: vi.fn(),
        updateItemInList: vi.fn(),
        removeItemFromList: vi.fn(),
        isCreatingList: false,
        isDeletingList: false,
        isAddingItem: false,
        isUpdatingItem: false,
        isRemovingItem: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);
      const appleItemRow = screen.getByText('Gala Apples').closest('tr')!;
      // Correct the title query to match the actual rendered title.
      const addToListButton = within(appleItemRow).getByTitle('Add Apples to list');
      expect(addToListButton).toBeInTheDocument();

      fireEvent.click(addToListButton!);
      expect(mockAddItemToList).toHaveBeenCalledWith(1, { masterItemId: 1 }); // The component calls the hook's function
    });

    it('should disable the add to list button if no list is active', () => {
      vi.mocked(useShoppingLists).mockReturnValue({
        activeListId: null,
        shoppingLists: [],
        addItemToList: mockAddItemToList,
        setActiveListId: vi.fn(),
        createList: vi.fn(),
        deleteList: vi.fn(),
        updateItemInList: vi.fn(),
        removeItemFromList: vi.fn(),
        isCreatingList: false,
        isDeletingList: false,
        isAddingItem: false,
        isUpdatingItem: false,
        isRemovingItem: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);
      // Multiple buttons will have this title, so we must use `getAllByTitle`.
      const addToListButtons = screen.getAllByTitle('Select a shopping list first');
      // Assert that at least one such button exists and that they are all disabled.
      expect(addToListButtons.length).toBeGreaterThan(0);
      addToListButtons.forEach((button) => expect(button).toBeDisabled());
    });

    it('should display the canonical name when it differs from the item name', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      // For 'Gala Apples', canonical is 'Apples'
      expect(screen.getByText('(Canonical: Apples)')).toBeInTheDocument();
      // For '2% Milk', canonical is 'Milk'
      expect(screen.getByText('(Canonical: Milk)')).toBeInTheDocument();
    });

    it('should not display the canonical name when it is the same as the item name (case-insensitive)', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      // Find the row for the item where item.name === canonical.name
      const matchingNameItemRow = screen.getByText('$2.50/lb').closest('tr')!;
      // Ensure the redundant canonical name is not displayed
      expect(within(matchingNameItemRow).queryByText(/\(Canonical: .*\)/)).not.toBeInTheDocument();
    });
  });

  describe('Sorting and Filtering', () => {
    it('should sort watched items to the top', () => {
      // Watch 'Chicken Breast' (normally 3rd) and 'Apples' (normally 1st)
      vi.mocked(useUserData).mockReturnValue({
        watchedItems: [mockMasterItems[2], mockMasterItems[0]],
        shoppingLists: [],
        isLoading: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);

      // Get all rows from the table body
      const rows = screen.getAllByRole('row');
      // Extract the primary item name from each row to check the sort order
      const itemNamesInOrder = rows.map(
        (row) => row.querySelector('div.font-semibold, div.font-bold')?.textContent,
      );

      // Assert the order is correct: watched items first, then others.
      // 'Gala Apples' (101) and 'Apples' (105) both have master_item_id 1, which is watched.
      // The implementation sorts watched items to the top, and then sorts alphabetically within each group (watched/unwatched).
      const expectedOrder = [
        'Apples', // Watched
        'Boneless Chicken', // Watched
        'Gala Apples', // Watched
        '2% Milk',
        'Mystery Soda', // Unwatched
      ];
      expect(itemNamesInOrder).toEqual(expectedOrder);
    });

    it('should filter items by category', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      const categoryFilter = screen.getByLabelText('Filter by category');

      // Initial state
      expect(screen.getByText('Gala Apples')).toBeInTheDocument();
      expect(screen.getByText('2% Milk')).toBeInTheDocument();

      // Filter by Dairy
      fireEvent.change(categoryFilter, { target: { value: 'Dairy' } });
      expect(screen.queryByText('Gala Apples')).not.toBeInTheDocument();
      expect(screen.getByText('2% Milk')).toBeInTheDocument();

      // Check empty state for filter
      fireEvent.change(categoryFilter, { target: { value: 'Produce' } });
      fireEvent.change(categoryFilter, { target: { value: 'Snacks' } }); // A category with no items
      expect(screen.getByText('No items found for the selected category.')).toBeInTheDocument();
    });

    it('should not render the category filter if there is only one category', () => {
      const singleCategoryItems = mockFlyerItems.filter((item) => item.category_name === 'Produce');
      render(<ExtractedDataTable {...defaultProps} items={singleCategoryItems} />);
      expect(screen.queryByLabelText('Filter by category')).not.toBeInTheDocument();
    });

    it('should allow switching filter back to All Categories', () => {
      render(<ExtractedDataTable {...defaultProps} />);
      const categoryFilter = screen.getByLabelText('Filter by category');

      // Filter to Dairy
      fireEvent.change(categoryFilter, { target: { value: 'Dairy' } });
      expect(screen.queryByText('Gala Apples')).not.toBeInTheDocument();
      expect(screen.getByText('2% Milk')).toBeInTheDocument();

      // Filter back to All
      fireEvent.change(categoryFilter, { target: { value: 'all' } });
      expect(screen.getByText('Gala Apples')).toBeInTheDocument();
      expect(screen.getByText('2% Milk')).toBeInTheDocument();
    });

    it('should sort items alphabetically within watched and unwatched groups', () => {
      const items = [
        createMockFlyerItem({
          flyer_item_id: 1,
          item: 'Yam',
          master_item_id: 3,
          category_name: 'Produce',
        }), // Unwatched
        createMockFlyerItem({
          flyer_item_id: 2,
          item: 'Zebra',
          master_item_id: 1,
          category_name: 'Produce',
        }), // Watched
        createMockFlyerItem({
          flyer_item_id: 3,
          item: 'Banana',
          master_item_id: 4,
          category_name: 'Produce',
        }), // Unwatched
        createMockFlyerItem({
          flyer_item_id: 4,
          item: 'Apple',
          master_item_id: 2,
          category_name: 'Produce',
        }), // Watched
      ];

      vi.mocked(useUserData).mockReturnValue({
        watchedItems: [
          createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Zebra' }),
          createMockMasterGroceryItem({ master_grocery_item_id: 2, name: 'Apple' }),
        ],
        shoppingLists: [],
        isLoading: false,
        error: null,
      });

      render(<ExtractedDataTable {...defaultProps} items={items} />);

      const rows = screen.getAllByRole('row');
      // Extract item names based on the bold/semibold classes used for names
      const itemNames = rows.map((row) => {
        const nameEl = row.querySelector('.font-bold, .font-semibold');
        return nameEl?.textContent;
      });

      // Expected: Watched items first (Apple, Zebra), then Unwatched (Banana, Yam)
      expect(itemNames).toEqual(['Apple', 'Zebra', 'Banana', 'Yam']);
    });
  });

  describe('Data Edge Cases', () => {
    it('should render correctly when masterItems list is empty', () => {
      vi.mocked(useMasterItems).mockReturnValue({
        masterItems: [], // No master items
        isLoading: false,
        error: null,
      });
      render(<ExtractedDataTable {...defaultProps} />);
      // No canonical names should be resolved or displayed
      expect(screen.queryByText(/\(Canonical: .*\)/)).not.toBeInTheDocument();
      // If canonical name isn't resolved (because masterItems is empty), the Add to list button should NOT appear
      const appleItemRow = screen.getByText('Gala Apples').closest('tr')!;
      expect(
        within(appleItemRow).queryByTitle('Select a shopping list first'),
      ).not.toBeInTheDocument();
    });

    it('should correctly format unit price for metric system', () => {
      render(<ExtractedDataTable {...defaultProps} unitSystem="metric" />);
      const chickenItemRow = screen.getByText('Boneless Chicken').closest('tr')!;

      // --- Debugging Logs Start ---
      console.log('--- DEBUG: Metric System Test Case ---');

      // 1. Log the full HTML structure of the row to see how text is nested
      console.log('DOM Structure:', prettyDOM(chickenItemRow));

      // 2. Log all text content in the row to verify if '$8.00' exists in isolation
      console.log('Row Text Content:', chickenItemRow?.textContent);

      // 3. Find all elements that look like price containers to see their exact text
      const potentialPrices = within(chickenItemRow).queryAllByText(/\$/);
      console.log(`Found ${potentialPrices.length} elements containing "$":`);
      potentialPrices.forEach((el, idx) => {
        console.log(`  [${idx}] Tag: <${el.tagName.toLowerCase()}>, Text: "${el.textContent}"`);
      });

      // 4. Test if strict matching is the cause of failure
      const strictMatch = within(chickenItemRow).queryByText('$8.00');
      const laxMatch = within(chickenItemRow).queryByText('$8.00', { exact: false });
      console.log(`Match Check: Strict='$8.00' found? ${!!strictMatch}`);
      console.log(`Match Check: Lax='$8.00' found? ${!!laxMatch}`);
      console.log('--- DEBUG End ---');
      // --- Debugging Logs End ---

      // Fix: Use { exact: false } because the text is "$8.00/kg" (broken into "$8.00" + "/kg" or just one string)
      // Based on the error log provided, it is likely inside a single span as "$8.00/kg"
      expect(within(chickenItemRow).getByText('$8.00', { exact: false })).toBeInTheDocument();

      // Check for the unit suffix, which might be in a separate element or part of the string
      expect(within(chickenItemRow).getAllByText(/\/kg/i).length).toBeGreaterThan(0);
    });

    it('should handle activeListId pointing to a non-existent list', () => {
      vi.mocked(useShoppingLists).mockReturnValue({
        activeListId: 999, // Non-existent
        shoppingLists: mockShoppingLists,
        addItemToList: mockAddItemToList,
        setActiveListId: vi.fn(),
        createList: vi.fn(),
        deleteList: vi.fn(),
        updateItemInList: vi.fn(),
        removeItemFromList: vi.fn(),
        isCreatingList: false,
        isDeletingList: false,
        isAddingItem: false,
        isUpdatingItem: false,
        isRemovingItem: false,
        error: null,
      });

      render(<ExtractedDataTable {...defaultProps} />);

      // Should behave as if item is not in list (Add button enabled)
      const appleItemRow = screen.getByText('Gala Apples').closest('tr')!;
      const addToListButton = within(appleItemRow).getByTitle('Add Apples to list');
      expect(addToListButton).toBeInTheDocument();
      expect(addToListButton).not.toBeDisabled();
    });

    it('should display numeric quantity in parentheses if available', () => {
      const itemWithQtyNum = createMockFlyerItem({
        flyer_item_id: 999,
        item: 'Bulk Rice',
        quantity: 'Bag',
        quantity_num: 5,
        unit_price: { value: 10, unit: 'kg' },
        category_name: 'Pantry',
        flyer_id: 1,
      });
      render(<ExtractedDataTable {...defaultProps} items={[itemWithQtyNum]} />);
      expect(screen.getByText('(5)')).toBeInTheDocument();
    });

    it('should use fallback category when adding to watchlist for items without category_name', () => {
      const itemWithoutCategory = createMockFlyerItem({
        flyer_item_id: 999,
        item: 'Mystery Item',
        master_item_id: 10,
        category_name: undefined,
        flyer_id: 1,
      });

      // Mock masterItems to include a matching item for canonical name resolution
      vi.mocked(useMasterItems).mockReturnValue({
        masterItems: [
          createMockMasterGroceryItem({
            master_grocery_item_id: 10,
            name: 'Canonical Mystery',
          }),
        ],
        isLoading: false,
        error: null,
      });

      render(<ExtractedDataTable {...defaultProps} items={[itemWithoutCategory]} />);

      const itemRow = screen.getByText('Mystery Item').closest('tr')!;
      const watchButton = within(itemRow).getByTitle("Add 'Canonical Mystery' to your watchlist");
      fireEvent.click(watchButton);

      expect(mockAddWatchedItem).toHaveBeenCalledWith('Canonical Mystery', 'Other/Miscellaneous');
    });

    it('should not call addItemToList when activeListId is null and button is clicked', () => {
      vi.mocked(useShoppingLists).mockReturnValue({
        activeListId: null,
        shoppingLists: [],
        addItemToList: mockAddItemToList,
        setActiveListId: vi.fn(),
        createList: vi.fn(),
        deleteList: vi.fn(),
        updateItemInList: vi.fn(),
        removeItemFromList: vi.fn(),
        isCreatingList: false,
        isDeletingList: false,
        isAddingItem: false,
        isUpdatingItem: false,
        isRemovingItem: false,
        error: null,
      });

      render(<ExtractedDataTable {...defaultProps} />);

      // Even with disabled button, test the handler logic by verifying no call is made
      // The buttons are disabled but we verify that even if clicked, no action occurs
      const addToListButtons = screen.getAllByTitle('Select a shopping list first');
      expect(addToListButtons.length).toBeGreaterThan(0);

      // Click the button (even though disabled)
      fireEvent.click(addToListButtons[0]);

      // addItemToList should not be called because activeListId is null
      expect(mockAddItemToList).not.toHaveBeenCalled();
    });
  });
});
