// src/hooks/useShoppingLists.test.tsx
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useShoppingLists } from './useShoppingLists';
import { useAuth } from '../hooks/useAuth';
import { useUserData } from '../hooks/useUserData';
import {
  useCreateShoppingListMutation,
  useDeleteShoppingListMutation,
  useAddShoppingListItemMutation,
  useUpdateShoppingListItemMutation,
  useRemoveShoppingListItemMutation,
} from './mutations';
import type { User } from '../types';
import {
  createMockShoppingList,
  createMockUser,
  createMockUserProfile,
} from '../tests/utils/mockFactories';

// Mock the hooks that useShoppingLists depends on
vi.mock('../hooks/useAuth');
vi.mock('../hooks/useUserData');
vi.mock('./mutations', () => ({
  useCreateShoppingListMutation: vi.fn(),
  useDeleteShoppingListMutation: vi.fn(),
  useAddShoppingListItemMutation: vi.fn(),
  useUpdateShoppingListItemMutation: vi.fn(),
  useRemoveShoppingListItemMutation: vi.fn(),
}));

const mockedUseAuth = vi.mocked(useAuth);
const mockedUseUserData = vi.mocked(useUserData);
const mockedUseCreateShoppingListMutation = vi.mocked(useCreateShoppingListMutation);
const mockedUseDeleteShoppingListMutation = vi.mocked(useDeleteShoppingListMutation);
const mockedUseAddShoppingListItemMutation = vi.mocked(useAddShoppingListItemMutation);
const mockedUseUpdateShoppingListItemMutation = vi.mocked(useUpdateShoppingListItemMutation);
const mockedUseRemoveShoppingListItemMutation = vi.mocked(useRemoveShoppingListItemMutation);

const mockUser: User = createMockUser({ user_id: 'user-123', email: 'test@example.com' });
const mockLists = [
  createMockShoppingList({ shopping_list_id: 1, name: 'Groceries', user_id: 'user-123' }),
  createMockShoppingList({ shopping_list_id: 2, name: 'Hardware', user_id: 'user-123' }),
];

describe('useShoppingLists Hook', () => {
  const mockMutateAsync = vi.fn();
  const createBaseMutation = () => ({
    mutateAsync: mockMutateAsync,
    mutate: vi.fn(),
    isPending: false,
    error: null,
    isError: false,
    isSuccess: false,
    isIdle: true,
  });

  const mockCreateMutation = createBaseMutation();
  const mockDeleteMutation = createBaseMutation();
  const mockAddItemMutation = createBaseMutation();
  const mockUpdateItemMutation = createBaseMutation();
  const mockRemoveItemMutation = createBaseMutation();

  beforeEach(() => {
    vi.resetAllMocks();

    // Mock all TanStack Query mutation hooks
    mockedUseCreateShoppingListMutation.mockReturnValue(mockCreateMutation as any);
    mockedUseDeleteShoppingListMutation.mockReturnValue(mockDeleteMutation as any);
    mockedUseAddShoppingListItemMutation.mockReturnValue(mockAddItemMutation as any);
    mockedUseUpdateShoppingListItemMutation.mockReturnValue(mockUpdateItemMutation as any);
    mockedUseRemoveShoppingListItemMutation.mockReturnValue(mockRemoveItemMutation as any);

    // Provide default implementation for auth
    mockedUseAuth.mockReturnValue({
      userProfile: createMockUserProfile({ user: mockUser }),
      authStatus: 'AUTHENTICATED',
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
      updateProfile: vi.fn(),
    });

    // Provide default implementation for user data (no more setters!)
    mockedUseUserData.mockReturnValue({
      shoppingLists: [],
      watchedItems: [],
      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 when lists exist', () => {
    mockedUseUserData.mockReturnValue({
      shoppingLists: mockLists,
      watchedItems: [],
      isLoading: false,
      error: null,
    });

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

    expect(result.current.activeListId).toBe(1);
  });

  it('should use TanStack Query mutation hooks', () => {
    renderHook(() => useShoppingLists());

    // Verify that all mutation hooks were called
    expect(mockedUseCreateShoppingListMutation).toHaveBeenCalled();
    expect(mockedUseDeleteShoppingListMutation).toHaveBeenCalled();
    expect(mockedUseAddShoppingListItemMutation).toHaveBeenCalled();
    expect(mockedUseUpdateShoppingListItemMutation).toHaveBeenCalled();
    expect(mockedUseRemoveShoppingListItemMutation).toHaveBeenCalled();
  });

  it('should expose loading states from mutations', () => {
    const loadingCreateMutation = { ...mockCreateMutation, isPending: true };
    mockedUseCreateShoppingListMutation.mockReturnValue(loadingCreateMutation as any);

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

    expect(result.current.isCreatingList).toBe(true);
  });

  describe('createList', () => {
    it('should call the mutation with correct parameters', async () => {
      mockMutateAsync.mockResolvedValue({});

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

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

      expect(mockMutateAsync).toHaveBeenCalledWith({ name: 'New List' });
    });

    it('should handle mutation errors gracefully', async () => {
      mockMutateAsync.mockRejectedValue(new Error('Failed to create'));

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

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

      // Should not throw - error is caught and logged
      expect(mockMutateAsync).toHaveBeenCalled();
    });
  });

  describe('deleteList', () => {
    it('should call the mutation with correct parameters', async () => {
      mockMutateAsync.mockResolvedValue({});

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

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

      expect(mockMutateAsync).toHaveBeenCalledWith({ listId: 1 });
    });

    it('should handle mutation errors gracefully', async () => {
      mockMutateAsync.mockRejectedValue(new Error('Failed to delete'));

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

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

      // Should not throw - error is caught and logged
      expect(mockMutateAsync).toHaveBeenCalled();
    });
  });

  describe('addItemToList', () => {
    it('should call the mutation with correct parameters for master item', async () => {
      mockMutateAsync.mockResolvedValue({});

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

      await act(async () => {
        await result.current.addItemToList(1, { masterItemId: 42 });
      });

      expect(mockMutateAsync).toHaveBeenCalledWith({
        listId: 1,
        item: { masterItemId: 42 },
      });
    });

    it('should call the mutation with correct parameters for custom item', async () => {
      mockMutateAsync.mockResolvedValue({});

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

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

      expect(mockMutateAsync).toHaveBeenCalledWith({
        listId: 1,
        item: { customItemName: 'Special Item' },
      });
    });

    it('should handle mutation errors gracefully', async () => {
      mockMutateAsync.mockRejectedValue(new Error('Failed to add item'));

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

      await act(async () => {
        await result.current.addItemToList(1, { masterItemId: 42 });
      });

      // Should not throw - error is caught and logged
      expect(mockMutateAsync).toHaveBeenCalled();
    });
  });

  describe('updateItemInList', () => {
    it('should call the mutation with correct parameters', async () => {
      mockMutateAsync.mockResolvedValue({});

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

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

      expect(mockMutateAsync).toHaveBeenCalledWith({
        itemId: 10,
        updates: { is_purchased: true },
      });
    });

    it('should handle mutation errors gracefully', async () => {
      mockMutateAsync.mockRejectedValue(new Error('Failed to update'));

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

      await act(async () => {
        await result.current.updateItemInList(10, { quantity: 5 });
      });

      // Should not throw - error is caught and logged
      expect(mockMutateAsync).toHaveBeenCalled();
    });
  });

  describe('removeItemFromList', () => {
    it('should call the mutation with correct parameters', async () => {
      mockMutateAsync.mockResolvedValue({});

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

      await act(async () => {
        await result.current.removeItemFromList(10);
      });

      expect(mockMutateAsync).toHaveBeenCalledWith({ itemId: 10 });
    });

    it('should handle mutation errors gracefully', async () => {
      mockMutateAsync.mockRejectedValue(new Error('Failed to remove'));

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

      await act(async () => {
        await result.current.removeItemFromList(999);
      });

      // Should not throw - error is caught and logged
      expect(mockMutateAsync).toHaveBeenCalled();
    });
  });

  describe('error handling', () => {
    it('should expose error from any mutation', () => {
      const errorMutation = {
        ...mockAddItemMutation,
        error: new Error('Add item failed'),
      };
      mockedUseAddShoppingListItemMutation.mockReturnValue(errorMutation as any);

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

      expect(result.current.error).toBe('Add item failed');
    });

    it('should consolidate errors from multiple mutations', () => {
      const createError = { ...mockCreateMutation, error: new Error('Create failed') };
      const deleteError = { ...mockDeleteMutation, error: new Error('Delete failed') };

      mockedUseCreateShoppingListMutation.mockReturnValue(createError as any);
      mockedUseDeleteShoppingListMutation.mockReturnValue(deleteError as any);

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

      // Should return the first error found
      expect(result.current.error).toBeTruthy();
    });
  });

  describe('activeListId management', () => {
    it('should allow setting active list manually', () => {
      mockedUseUserData.mockReturnValue({
        shoppingLists: mockLists,
        watchedItems: [],
        isLoading: false,
        error: null,
      });

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

      act(() => {
        result.current.setActiveListId(2);
      });

      expect(result.current.activeListId).toBe(2);
    });

    it('should reset active list when all lists are deleted', () => {
      // Start with lists
      mockedUseUserData.mockReturnValue({
        shoppingLists: mockLists,
        watchedItems: [],
        isLoading: false,
        error: null,
      });

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

      expect(result.current.activeListId).toBe(1);

      // Update to no lists
      mockedUseUserData.mockReturnValue({
        shoppingLists: [],
        watchedItems: [],
        isLoading: false,
        error: null,
      });

      rerender();

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

    it('should select first list when active list is deleted', () => {
      // Start with 2 lists, second one active
      mockedUseUserData.mockReturnValue({
        shoppingLists: mockLists,
        watchedItems: [],
        isLoading: false,
        error: null,
      });

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

      act(() => {
        result.current.setActiveListId(2);
      });

      expect(result.current.activeListId).toBe(2);

      // Remove second list (only first remains)
      mockedUseUserData.mockReturnValue({
        shoppingLists: [mockLists[0]],
        watchedItems: [],
        isLoading: false,
        error: null,
      });

      rerender();

      // Should auto-select the first (and only) list
      expect(result.current.activeListId).toBe(1);
    });
  });

  it('should not perform actions if user is not authenticated', async () => {
    mockedUseAuth.mockReturnValue({
      userProfile: 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('Test');
      await result.current.deleteList(1);
      await result.current.addItemToList(1, { masterItemId: 1 });
      await result.current.updateItemInList(1, { quantity: 1 });
      await result.current.removeItemFromList(1);
    });

    // Mutations should not be called when user is not authenticated
    expect(mockMutateAsync).not.toHaveBeenCalled();
  });
});
