// src/hooks/useApi.test.ts
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useApi } from './useApi';

// Mock dependencies
const mockApiFunction = vi.fn();
vi.mock('../services/logger.client', () => ({
  logger: {
    error: vi.fn(),
    info: vi.fn(),
  },
}));
vi.mock('../services/notificationService', () => ({
  notifyError: vi.fn(),
}));

describe('useApi Hook', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should initialize with correct default states', () => {
    const { result } = renderHook(() => useApi(mockApiFunction));

    expect(result.current.data).toBeNull();
    expect(result.current.loading).toBe(false);
    expect(result.current.isRefetching).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('should set loading to true and return data on successful execution', async () => {
    const mockData = { id: 1, name: 'Test' };
    mockApiFunction.mockResolvedValue(new Response(JSON.stringify(mockData)));

    const { result } = renderHook(() => useApi<typeof mockData, [string]>(mockApiFunction));

    let promise: Promise<typeof mockData | null>;
    act(() => {
      promise = result.current.execute('test-arg');
    });

    expect(result.current.loading).toBe(true);

    await act(async () => {
      await promise;
    });

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toEqual(mockData);
    expect(result.current.error).toBeNull();
    expect(mockApiFunction).toHaveBeenCalledWith('test-arg', expect.any(AbortSignal));
  });

  it('should set error state on failed execution', async () => {
    const mockError = new Error('API Failure');
    mockApiFunction.mockRejectedValue(mockError);

    const { result } = renderHook(() => useApi(mockApiFunction));

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

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toBeNull();
    expect(result.current.error).toEqual(mockError);
  });

  it('should handle 204 No Content responses correctly', async () => {
    mockApiFunction.mockResolvedValue(new Response(null, { status: 204 }));

    const { result } = renderHook(() => useApi(mockApiFunction));

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

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toBeNull();
    expect(result.current.error).toBeNull();
  });

  it('should reset the state to initial values when reset is called', async () => {
    const mockData = { id: 1, name: 'Test Data' };
    mockApiFunction.mockResolvedValue(new Response(JSON.stringify(mockData)));

    const { result } = renderHook(() => useApi(mockApiFunction));

    // First, execute to populate the state
    await act(async () => {
      await result.current.execute();
    });

    // Assert that state is populated
    expect(result.current.data).toEqual(mockData);
    expect(result.current.loading).toBe(false);

    // Now, call reset
    act(() => {
      result.current.reset();
    });

    // Assert that state is back to initial values
    expect(result.current.data).toBeNull();
    expect(result.current.loading).toBe(false);
    expect(result.current.isRefetching).toBe(false);
    expect(result.current.error).toBeNull();
  });

  describe('isRefetching state', () => {
    it('should set isRefetching to true on second call, but not first', async () => {
      mockApiFunction.mockResolvedValue(new Response(JSON.stringify({ data: 'first call' })));
      // Provide the generic type argument to useApi
      const { result } = renderHook(() => useApi<{ data: string }, []>(mockApiFunction));

      // --- First call ---
      let firstCallPromise: Promise<{ data: string; } | null>;
      act(() => {
        firstCallPromise = result.current.execute();
      });

      // During the first call, loading is true, but isRefetching is false
      expect(result.current.loading).toBe(true);
      expect(result.current.isRefetching).toBe(false);

      await act(async () => { await firstCallPromise; });

      // After the first call, both are false
      expect(result.current.loading).toBe(false);
      expect(result.current.isRefetching).toBe(false);
      expect(result.current.data).toEqual({ data: 'first call' });

      // --- Second call ---
      mockApiFunction.mockResolvedValue(new Response(JSON.stringify({ data: 'second call' })));
      let secondCallPromise: Promise<{ data: string; } | null>;
      act(() => {
        secondCallPromise = result.current.execute();
      });

      // During the second call, both loading and isRefetching are true
      expect(result.current.loading).toBe(true);
      expect(result.current.isRefetching).toBe(true);

      await act(async () => { await secondCallPromise; });

      // After the second call, both are false again
      expect(result.current.loading).toBe(false);
      expect(result.current.isRefetching).toBe(false);
      expect(result.current.data).toEqual({ data: 'second call' });
    });
  });
});