// src/hooks/useUserData.test.tsx
import React, { ReactNode } from 'react';
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useUserData } from './useUserData';
import { useAuth } from './useAuth';
import { UserDataProvider } from '../providers/UserDataProvider';
import { useWatchedItemsQuery } from './queries/useWatchedItemsQuery';
import { useShoppingListsQuery } from './queries/useShoppingListsQuery';
import type { UserProfile } from '../types';
import {
  createMockMasterGroceryItem,
  createMockShoppingList,
  createMockUserProfile,
} from '../tests/utils/mockFactories';

// 1. Mock the hook's dependencies
vi.mock('../hooks/useAuth');
vi.mock('./queries/useWatchedItemsQuery');
vi.mock('./queries/useShoppingListsQuery');

// 2. Create typed mocks for type safety and autocompletion
const mockedUseAuth = vi.mocked(useAuth);
const mockedUseWatchedItemsQuery = vi.mocked(useWatchedItemsQuery);
const mockedUseShoppingListsQuery = vi.mocked(useShoppingListsQuery);

// 3. A simple wrapper component that renders our provider.
// This is necessary because the useUserData hook needs to be a child of UserDataProvider.
const wrapper = ({ children }: { children: ReactNode }) => (
  <UserDataProvider>{children}</UserDataProvider>
); // No change needed here, just for context

// 4. Mock data for testing
const mockUser: UserProfile = createMockUserProfile({
  full_name: 'Test User',
  points: 100,
  user: { user_id: 'user-123', email: 'test@example.com' },
});

const mockWatchedItems = [createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Milk' })];

const mockShoppingLists = [
  createMockShoppingList({ shopping_list_id: 1, name: 'Weekly Groceries', user_id: 'user-123' }),
];

describe('useUserData Hook and UserDataProvider', () => {
  beforeEach(() => {
    // Clear mock history before each test to ensure test isolation.
    vi.clearAllMocks();
  });

  it('should throw an error if useUserData is used outside of UserDataProvider', () => {
    // 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(() => useUserData())).toThrow(
      'useUserData must be used within a UserDataProvider',
    );

    // Restore the original console.error function.
    console.error = originalError;
  });

  it('should return initial empty state when user is not authenticated', () => {
    // Arrange: Simulate a logged-out user.
    mockedUseAuth.mockReturnValue({
      userProfile: null,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    // Arrange: Mock the return value of the inner hooks.
    mockedUseWatchedItemsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
    } as any);

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

    // Assert: Check that the context values are in their initial, empty state.
    expect(result.current.isLoading).toBe(false);
    expect(result.current.watchedItems).toEqual([]);
    expect(result.current.shoppingLists).toEqual([]);
    expect(result.current.error).toBeNull();
    // Assert: Check that queries were disabled (called with false)
    expect(mockedUseWatchedItemsQuery).toHaveBeenCalledWith(false);
    expect(mockedUseShoppingListsQuery).toHaveBeenCalledWith(false);
  });

  it('should return loading state when user is authenticated and data is fetching', () => {
    // Arrange: Simulate a logged-in user.
    mockedUseAuth.mockReturnValue({
      userProfile: mockUser,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    // Arrange: Mock one of the inner hooks to be in a loading state.
    mockedUseWatchedItemsQuery.mockReturnValue({
      data: undefined,
      isLoading: true,
      error: null,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: undefined,
      isLoading: false,
      error: null,
    } as any);

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

    // Assert
    expect(result.current.isLoading).toBe(true);
  });

  it('should return data on successful fetch when user is authenticated', async () => {
    // Arrange: Simulate a logged-in user.
    mockedUseAuth.mockReturnValue({
      userProfile: mockUser,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    // Arrange: Mock successful data fetches for both inner hooks.
    mockedUseWatchedItemsQuery.mockReturnValue({
      data: mockWatchedItems,
      isLoading: false,
      error: null,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: mockShoppingLists,
      isLoading: false,
      error: null,
    } as any);

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

    // Assert: Use `waitFor` to allow the `useEffect` hook to update the state.
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.watchedItems).toEqual(mockWatchedItems);
      expect(result.current.shoppingLists).toEqual(mockShoppingLists);
      expect(result.current.error).toBeNull();
    });
  });

  it('should return an error state if one of the fetches fails', async () => {
    // Arrange: Simulate a logged-in user.
    mockedUseAuth.mockReturnValue({
      userProfile: mockUser,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    const mockError = new Error('Failed to fetch watched items');

    mockedUseWatchedItemsQuery.mockReturnValue({
      data: undefined,
      isLoading: false,
      error: mockError,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: mockShoppingLists,
      isLoading: false,
      error: null,
    } as any);

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

    // Assert
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
      expect(result.current.error).toBe('Failed to fetch watched items');
      // Data that was fetched successfully should still be populated.
      expect(result.current.shoppingLists).toEqual(mockShoppingLists);
      // Data that failed should be empty.
      expect(result.current.watchedItems).toEqual([]);
    });
  });

  it('should clear data when the user logs out', async () => {
    // Arrange: Start with a logged-in user and data.
    mockedUseAuth.mockReturnValue({
      userProfile: mockUser,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    mockedUseWatchedItemsQuery.mockReturnValue({
      data: mockWatchedItems,
      isLoading: false,
      error: null,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: mockShoppingLists,
      isLoading: false,
      error: null,
    } as any);
    const { result, rerender } = renderHook(() => useUserData(), { wrapper });
    await waitFor(() => expect(result.current.watchedItems).not.toEqual([]));

    // Act: Simulate logout by re-rendering with a null user.
    mockedUseAuth.mockReturnValue({
      userProfile: null,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    // Update mocks to return empty data for the logged out state
    mockedUseWatchedItemsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
    } as any);
    mockedUseShoppingListsQuery.mockReturnValue({
      data: [],
      isLoading: false,
      error: null,
    } as any);

    rerender();

    // Assert: The data should now be cleared.
    expect(result.current.watchedItems).toEqual([]);
    expect(result.current.shoppingLists).toEqual([]);
  });
});
