// src/hooks/useActiveDeals.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { useActiveDeals } from './useActiveDeals';
import * as apiClient from '../services/apiClient';
import { useFlyers } from '../hooks/useFlyers';
import { useUserData } from '../hooks/useUserData';
import type { Flyer, MasterGroceryItem, FlyerItem, DealItem } from '../types';

// The apiClient is globally mocked in our test setup, so we just need to cast it
const mockedApiClient = vi.mocked(apiClient);

// Mock the new data provider hooks
vi.mock('../hooks/useFlyers');
vi.mock('../hooks/useUserData');

// Create typed mocks for easier usage
const mockedUseFlyers = vi.mocked(useFlyers);
const mockedUseUserData = vi.mocked(useUserData);

// Mock the logger to prevent console noise
vi.mock('../services/logger.client', () => ({
  logger: {
    error: vi.fn(),
  },
}));

// Set a consistent "today" for testing flyer validity to make tests deterministic
const TODAY = new Date('2024-01-15T12:00:00.000Z');

describe('useActiveDeals Hook', () => {
  // Use fake timers to control the current date in tests
  beforeEach(() => {
    // FIX: Only fake the 'Date' object. 
    // This allows `new Date()` to be mocked (via setSystemTime) while keeping 
    // `setTimeout`/`setInterval` native so `waitFor` doesn't hang.
    vi.useFakeTimers({ toFake: ['Date'] });
    vi.setSystemTime(TODAY);
    vi.clearAllMocks();

    // Set up default successful mocks for the new data hooks
    mockedUseFlyers.mockReturnValue({
      flyers: mockFlyers,
      isLoadingFlyers: false,
      flyersError: null,
      fetchNextFlyersPage: vi.fn(),
      hasNextFlyersPage: false,
      isRefetchingFlyers: false,
      refetchFlyers: vi.fn(),
    });
    mockedUseUserData.mockReturnValue({
      watchedItems: mockWatchedItems,
      shoppingLists: [],
      setWatchedItems: vi.fn(),
      setShoppingLists: vi.fn(),
      isLoading: false,
      error: null,
    });
  });

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

  const mockFlyers: Flyer[] = [
    // A currently valid flyer
    { flyer_id: 1, file_name: 'valid.pdf', image_url: '', item_count: 10, created_at: '', valid_from: '2024-01-10', valid_to: '2024-01-20', store: { store_id: 1, name: 'Valid Store', created_at: '', logo_url: '' } },
    // An expired flyer
    { flyer_id: 2, file_name: 'expired.pdf', image_url: '', item_count: 5, created_at: '', valid_from: '2024-01-01', valid_to: '2024-01-05', store: { store_id: 2, name: 'Expired Store', created_at: '', logo_url: '' } },
    // A future flyer
    { flyer_id: 3, file_name: 'future.pdf', image_url: '', item_count: 8, created_at: '', valid_from: '2024-02-01', valid_to: '2024-02-10', store: { store_id: 3, name: 'Future Store', created_at: '', logo_url: '' } },
  ];

  const mockWatchedItems: MasterGroceryItem[] = [
    { master_grocery_item_id: 101, name: 'Apples', created_at: '' },
    { master_grocery_item_id: 102, name: 'Milk', created_at: '' },
  ];

  const mockFlyerItems: FlyerItem[] = [
    // A deal for a watched item in a valid flyer
    { flyer_item_id: 1, flyer_id: 1, item: 'Red Apples', price_display: '$1.99', price_in_cents: 199, quantity: 'lb', master_item_id: 101, master_item_name: 'Apples', created_at: '', view_count: 0, click_count: 0, updated_at: '' },
    // An item that is not a deal
    { flyer_item_id: 2, flyer_id: 1, item: 'Oranges', price_display: '$2.49', price_in_cents: 249, quantity: 'lb', master_item_id: 201, created_at: '', view_count: 0, click_count: 0, updated_at: '' },
  ];

  it('should return loading state initially and then calculated data', async () => {
    mockedApiClient.countFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify({ count: 10 })));
    mockedApiClient.fetchFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify(mockFlyerItems)));

    const { result } = renderHook(() => useActiveDeals());

    // The hook runs the effect almost immediately. We shouldn't strictly assert false 
    // because depending on render timing, it might already be true. 
    // We mainly care that it eventually resolves.

    // Wait for the hook's useEffect to run and complete
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.totalActiveItems).toBe(10);
      expect(result.current.activeDeals).toHaveLength(1);
      expect(result.current.activeDeals[0].item).toBe('Red Apples');
    });
  });

  it('should correctly filter for valid flyers and make API calls with their IDs', async () => {
    mockedApiClient.countFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify({ count: 0 })));
    mockedApiClient.fetchFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify([])));

    const { result } = renderHook(() => useActiveDeals());

    await waitFor(() => {
      // Only the valid flyer (id: 1) should be used in the API calls
      // The second argument is an AbortSignal, which we can match with expect.anything()
      expect(mockedApiClient.countFlyerItemsForFlyers).toHaveBeenCalledWith([1], expect.anything());
      expect(mockedApiClient.fetchFlyerItemsForFlyers).toHaveBeenCalledWith([1], expect.anything());
      expect(result.current.isLoading).toBe(false);
    });
  });

  it('should not fetch flyer items if there are no watched items', async () => {
    mockedApiClient.countFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify({ count: 10 })));
    mockedUseUserData.mockReturnValue({
      watchedItems: [],
      shoppingLists: [],
      setWatchedItems: vi.fn(),
      setShoppingLists: vi.fn(),
      isLoading: false,
      error: null,
    }); // Override for this test

    const { result } = renderHook(() => useActiveDeals());

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.totalActiveItems).toBe(10);
      expect(result.current.activeDeals).toEqual([]);
      // The key assertion: fetchFlyerItemsForFlyers should not be called
      expect(mockedApiClient.fetchFlyerItemsForFlyers).not.toHaveBeenCalled();
    });
  });

  it('should handle the case where there are no valid flyers', async () => {
    // Override flyers mock to only include invalid ones
    mockedUseFlyers.mockReturnValue({
      flyers: [mockFlyers[1], mockFlyers[2]],
      isLoadingFlyers: false,
      flyersError: null,
      fetchNextFlyersPage: vi.fn(),
      hasNextFlyersPage: false,
      isRefetchingFlyers: false,
      refetchFlyers: vi.fn(),
    });
    const { result } = renderHook(() => useActiveDeals());

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.totalActiveItems).toBe(0);
      expect(result.current.activeDeals).toEqual([]);
      // No API calls should be made if there are no valid flyers
      expect(mockedApiClient.countFlyerItemsForFlyers).not.toHaveBeenCalled();
      expect(mockedApiClient.fetchFlyerItemsForFlyers).not.toHaveBeenCalled();
    });
  });

  it('should set an error state if an API call fails', async () => {
    const apiError = new Error('Network Failure');
    mockedApiClient.countFlyerItemsForFlyers.mockRejectedValue(apiError);

    const { result } = renderHook(() => useActiveDeals());

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.error).toBe('Could not fetch active deals or totals: Network Failure');
    });
  });

  it('should correctly map flyer items to DealItem format', async () => {
    mockedApiClient.countFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify({ count: 10 })));
    mockedApiClient.fetchFlyerItemsForFlyers.mockResolvedValue(new Response(JSON.stringify(mockFlyerItems)));

    const { result } = renderHook(() => useActiveDeals());

    await waitFor(() => {
      const deal = result.current.activeDeals[0];
      const expectedDeal: DealItem = {
        item: 'Red Apples',
        price_display: '$1.99',
        price_in_cents: 199,
        quantity: 'lb',
        storeName: 'Valid Store',
        master_item_name: 'Apples',
        unit_price: undefined, // or mock a value if needed
      };
      expect(deal).toEqual(expectedDeal);
    });
  });
});