// src/hooks/useFlyers.test.tsx
import React, { ReactNode } from 'react';
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useFlyers } from './useFlyers';
import { FlyersProvider } from '../providers/FlyersProvider';
import { useInfiniteQuery } from './useInfiniteQuery';
import type { Flyer } from '../types';

// 1. Mock the useInfiniteQuery hook, which is the dependency of our FlyersProvider.
vi.mock('./useInfiniteQuery');

// 2. Create a typed mock of the hook for type safety and autocompletion.
const mockedUseInfiniteQuery = vi.mocked(useInfiniteQuery);

// 3. A simple wrapper component that renders our provider.
// This is necessary because the useFlyers hook needs to be a child of FlyersProvider.
const wrapper = ({ children }: { children: ReactNode }) => <FlyersProvider>{children}</FlyersProvider>;

describe('useFlyers Hook and FlyersProvider', () => {
  // Create mock functions that we can spy on to see if they are called.
  const mockFetchNextPage = vi.fn();
  const mockRefetch = vi.fn();

  beforeEach(() => {
    // Clear mock history before each test to ensure test isolation.
    vi.clearAllMocks();
  });

  it('should throw an error if useFlyers is used outside of FlyersProvider', () => {
    // Suppress the expected console.error for this test to keep the output clean.
    const originalError = console.error;
    console.error = vi.fn();
    
    // Expecting renderHook to throw an error because there's no provider.
    expect(() => renderHook(() => useFlyers())).toThrow('useFlyers must be used within a FlyersProvider');
    
    // Restore the original console.error function.
    console.error = originalError;
  });

  it('should return the initial loading state correctly', () => {
    // Arrange: Configure the mocked hook to return a loading state.
    mockedUseInfiniteQuery.mockReturnValue({
      data: [],
      isLoading: true,
      error: null,
      fetchNextPage: mockFetchNextPage,
      hasNextPage: false,
      refetch: mockRefetch,
      isRefetching: false,
      isFetchingNextPage: false,
    });

    // Act: Render the hook within the provider wrapper.
    const { result } = renderHook(() => useFlyers(), { wrapper });

    // Assert: Check that the context values match the loading state.
    expect(result.current.isLoadingFlyers).toBe(true);
    expect(result.current.flyers).toEqual([]);
    expect(result.current.flyersError).toBeNull();
  });

  it('should return flyers data and hasNextPage on successful fetch', () => {
    // Arrange: Mock a successful data fetch.
    const mockFlyers: Flyer[] = [
      { flyer_id: 1, file_name: 'flyer1.jpg', image_url: 'url1', item_count: 5, created_at: '2024-01-01' },
    ];
    mockedUseInfiniteQuery.mockReturnValue({
      data: mockFlyers,
      isLoading: false,
      error: null,
      fetchNextPage: mockFetchNextPage,
      hasNextPage: true,
      refetch: mockRefetch,
      isRefetching: false,
      isFetchingNextPage: false,
    });

    // Act
    const { result } = renderHook(() => useFlyers(), { wrapper });

    // Assert
    expect(result.current.isLoadingFlyers).toBe(false);
    expect(result.current.flyers).toEqual(mockFlyers);
    expect(result.current.hasNextFlyersPage).toBe(true);
  });

  it('should return an error state if the fetch fails', () => {
    // Arrange: Mock a failed data fetch.
    const mockError = new Error('Failed to fetch');
    mockedUseInfiniteQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: mockError,
      fetchNextPage: mockFetchNextPage,
      hasNextPage: false,
      refetch: mockRefetch,
      isRefetching: false,
      isFetchingNextPage: false,
    });

    // Act
    const { result } = renderHook(() => useFlyers(), { wrapper });

    // Assert
    expect(result.current.isLoadingFlyers).toBe(false);
    expect(result.current.flyers).toEqual([]);
    expect(result.current.flyersError).toBe(mockError);
  });

  it('should call fetchNextFlyersPage when the context function is invoked', () => {
    // Arrange
    mockedUseInfiniteQuery.mockReturnValue({
      data: [], isLoading: false, error: null, hasNextPage: true, isRefetching: false, isFetchingNextPage: false,
      fetchNextPage: mockFetchNextPage, // Pass the mock function
      refetch: mockRefetch,
    });
    const { result } = renderHook(() => useFlyers(), { wrapper });

    // Act: Use `act` to wrap state updates.
    act(() => {
      result.current.fetchNextFlyersPage();
    });

    // Assert
    expect(mockFetchNextPage).toHaveBeenCalledTimes(1);
  });

  it('should call refetchFlyers when the context function is invoked', () => {
    // Arrange
    mockedUseInfiniteQuery.mockReturnValue({ data: [], isLoading: false, error: null, hasNextPage: false, isRefetching: false, isFetchingNextPage: false, fetchNextPage: mockFetchNextPage, refetch: mockRefetch });
    const { result } = renderHook(() => useFlyers(), { wrapper });

    // Act
    act(() => { result.current.refetchFlyers(); });

    // Assert
    expect(mockRefetch).toHaveBeenCalledTimes(1);
  });
});