// src/hooks/useShoppingLists.test.tsx
import { renderHook, act, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useShoppingLists } from './useShoppingLists';
import { useApi } from './useApi';
import { useAuth } from '../hooks/useAuth';
import { useUserData } from '../hooks/useUserData';
import type { ShoppingList, ShoppingListItem, User } from '../types';

// Mock the hooks that useShoppingLists depends on
vi.mock('./useApi');
vi.mock('../hooks/useAuth');
vi.mock('../hooks/useUserData');

// The apiClient is globally mocked in our test setup, so we just need to cast it
const mockedUseApi = vi.mocked(useApi);
const mockedUseAuth = vi.mocked(useAuth);
const mockedUseUserData = vi.mocked(useUserData);

const mockUser: User = { user_id: 'user-123', email: 'test@example.com' };

describe('useShoppingLists Hook', () => {
  // Create a mock setter function that we can spy on
  const mockSetShoppingLists = vi.fn();

  // Create mock execute functions for each API operation
  const mockCreateListApi = vi.fn();
  const mockDeleteListApi = vi.fn();
  const mockAddItemApi = vi.fn();
  const mockUpdateItemApi = vi.fn();
  const mockRemoveItemApi = vi.fn();

  beforeEach(() => {
    // Reset all mocks before each test to ensure isolation
    vi.clearAllMocks();

    // Define the sequence of mocks corresponding to the hook's useApi calls
    const apiMocks = [
      { execute: mockCreateListApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
      { execute: mockDeleteListApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
      { execute: mockAddItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
      { execute: mockUpdateItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
      { execute: mockRemoveItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
    ];

    let callCount = 0;
    mockedUseApi.mockImplementation(() => {
      const mock = apiMocks[callCount % apiMocks.length];
      callCount++;
      return mock;
    });

    mockedUseAuth.mockReturnValue({
      user: mockUser,
      profile: null,
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });

    mockedUseUserData.mockReturnValue({
      shoppingLists: [],
      setShoppingLists: mockSetShoppingLists,
      watchedItems: [],
      setWatchedItems: vi.fn(),
      isLoading: false,
      error: null,
    });
  });

  it('should initialize with no active list when there are no lists', () => {
    const { result } = renderHook(() => useShoppingLists());

    expect(result.current.shoppingLists).toEqual([]);
    expect(result.current.activeListId).toBeNull();
  });

  it('should set the first list as active on initial load if lists exist', async () => {
    const mockLists: ShoppingList[] = [
      { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [] },
      { shopping_list_id: 2, name: 'Hardware Store', user_id: 'user-123', created_at: '', items: [] },
    ];

    mockedUseUserData.mockReturnValue({
      shoppingLists: mockLists,
      setShoppingLists: mockSetShoppingLists,
      watchedItems: [],
      setWatchedItems: vi.fn(),
      isLoading: false,
      error: null,
    });

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

    await waitFor(() => expect(result.current.activeListId).toBe(1));
  });

  it('should not set an active list if the user is not authenticated', () => {
    mockedUseAuth.mockReturnValue({
      user: null,
      profile: null,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });
    const mockLists: ShoppingList[] = [
      { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [] },
    ];
    mockedUseUserData.mockReturnValue({
      shoppingLists: mockLists,
      setShoppingLists: mockSetShoppingLists,
      watchedItems: [],
      setWatchedItems: vi.fn(),
      isLoading: false,
      error: null,
    });

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

    expect(result.current.activeListId).toBeNull();
  });

  describe('createList', () => {
    it('should call the API and update state on successful creation', async () => {
      const newList: ShoppingList = { shopping_list_id: 99, name: 'New List', user_id: 'user-123', created_at: '', items: [] };
      let currentLists: ShoppingList[] = [];

      // Mock the implementation of the setter to simulate a real state update.
      // This will cause the hook to re-render with the new list.
      mockSetShoppingLists.mockImplementation((updater) => {
        currentLists = typeof updater === 'function' ? updater(currentLists) : updater;
      });

      // The hook will now see the updated `currentLists` on re-render.
      mockedUseUserData.mockImplementation(() => ({
        shoppingLists: currentLists,
        setShoppingLists: mockSetShoppingLists,
        watchedItems: [],
        setWatchedItems: vi.fn(),
        isLoading: false,
        error: null,
      }));

      mockCreateListApi.mockResolvedValue(newList);

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

      // `act` ensures that all state updates from the hook are processed before assertions are made
      await act(async () => {
        await result.current.createList('New List');
      });

      expect(mockCreateListApi).toHaveBeenCalledWith('New List');
      expect(currentLists).toEqual([newList]);
    });

    it('should set an error message if API call fails', async () => {
      // Define a sequence with an error for the first call
      const apiMocksWithError = [
        { execute: vi.fn(), error: new Error('API Failed'), loading: false, isRefetching: false, data: null, reset: vi.fn() }, // Create with Error
        { execute: mockDeleteListApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
        { execute: mockAddItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
        { execute: mockUpdateItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
        { execute: mockRemoveItemApi, error: null, loading: false, isRefetching: false, data: null, reset: vi.fn() },
      ];

      let callCount = 0;
      mockedUseApi.mockImplementation(() => {
        const mock = apiMocksWithError[callCount % apiMocksWithError.length];
        callCount++;
        return mock;
      });

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

      await act(async () => {
        await result.current.createList('New List');
      });

      expect(result.current.error).toBe('API Failed');
    });
  });

  describe('deleteList', () => {
    it('should call the API and update state on successful deletion', async () => {
      const mockLists: ShoppingList[] = [
        { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [] },
        { shopping_list_id: 2, name: 'Hardware Store', user_id: 'user-123', created_at: '', items: [] },
      ];
      mockedUseUserData.mockReturnValue({
        shoppingLists: mockLists,
        setShoppingLists: mockSetShoppingLists,
        watchedItems: [],
        setWatchedItems: vi.fn(),
        isLoading: false,
        error: null,
      });
      mockDeleteListApi.mockResolvedValue(null); // Successful delete returns null

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

      await act(async () => {
        await result.current.deleteList(1);
      });

    await waitFor(() => expect(mockDeleteListApi).toHaveBeenCalledWith(1));
      // Check that the global state setter was called with the correctly filtered list
      expect(mockSetShoppingLists).toHaveBeenCalledWith([mockLists[1]]);
    });

    it('should update activeListId if the active list is deleted', async () => {
      const mockLists: ShoppingList[] = [
        { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [] },
        { shopping_list_id: 2, name: 'Hardware Store', user_id: 'user-123', created_at: '', items: [] },
      ];
      mockedUseUserData.mockReturnValue({
        shoppingLists: mockLists,
        setShoppingLists: mockSetShoppingLists,
        watchedItems: [],
        setWatchedItems: vi.fn(),
        isLoading: false,
        error: null,
      });
      mockDeleteListApi.mockResolvedValue(null);

      // Render the hook and wait for the initial effect to set activeListId
      const { result } = renderHook(() => useShoppingLists());
      await waitFor(() => expect(result.current.activeListId).toBe(1));

      await act(async () => {
        await result.current.deleteList(1);
      });

      // After deletion, the hook should select the next available list as active
      await waitFor(() => expect(result.current.activeListId).toBe(2));
    });
  });

  describe('addItemToList', () => {
    it('should call API and add item to the correct list', async () => {
        const mockLists: ShoppingList[] = [
            { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [] },
        ];
        const newItem: ShoppingListItem = { shopping_list_item_id: 101, shopping_list_id: 1, custom_item_name: 'Milk', is_purchased: false, quantity: 1, added_at: new Date().toISOString() };
        mockedUseUserData.mockReturnValue({
          shoppingLists: mockLists,
          setShoppingLists: mockSetShoppingLists,
          watchedItems: [],
          setWatchedItems: vi.fn(),
          isLoading: false,
          error: null,
        });
        mockAddItemApi.mockResolvedValue(newItem);

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

        await act(async () => {
            await result.current.addItemToList(1, { customItemName: 'Milk' });
        });

        expect(mockAddItemApi).toHaveBeenCalledWith(1, { customItemName: 'Milk' });
        const updater = mockSetShoppingLists.mock.calls[0][0];
        const newState = updater(mockLists);
        expect(newState[0].items).toHaveLength(1);
        expect(newState[0].items[0]).toEqual(newItem);
    });
  });

  describe('updateItemInList', () => {
    it('should call API and update the correct item', async () => {
        const initialItem: ShoppingListItem = { shopping_list_item_id: 101, shopping_list_id: 1, custom_item_name: 'Milk', is_purchased: false, quantity: 1, added_at: new Date().toISOString() };
        const mockLists: ShoppingList[] = [
            { shopping_list_id: 1, name: 'Groceries', user_id: 'user-123', created_at: '', items: [initialItem] },
        ];
        const updatedItem: ShoppingListItem = { ...initialItem, is_purchased: true };
        mockedUseUserData.mockReturnValue({
          shoppingLists: mockLists,
          setShoppingLists: mockSetShoppingLists,
          watchedItems: [],
          setWatchedItems: vi.fn(),
          isLoading: false,
          error: null,
        });
        mockUpdateItemApi.mockResolvedValue(updatedItem);

        const { result } = renderHook(() => useShoppingLists());
        act(() => { result.current.setActiveListId(1); }); // Set active list

        await act(async () => {
            await result.current.updateItemInList(101, { is_purchased: true });
        });

        expect(mockUpdateItemApi).toHaveBeenCalledWith(101, { is_purchased: true });
        const updater = mockSetShoppingLists.mock.calls[0][0];
        const newState = updater(mockLists);
        expect(newState[0].items[0].is_purchased).toBe(true);
    });
  });

  it('should not perform actions if user is not authenticated', async () => {
    mockedUseAuth.mockReturnValue({
      user: null,
      profile: null,
      authStatus: 'SIGNED_OUT',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });

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

    await act(async () => {
      await result.current.createList('Should not work');
    });

    expect(mockCreateListApi).not.toHaveBeenCalled();
  });
});