import { renderHook, act } from '@testing-library/react';

import { useAsyncAction } from './useAsyncAction';

describe('useAsyncAction', () => {
  it('starts in idle state', () => {
    const { result } = renderHook(() => useAsyncAction());
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('sets isLoading to true during execution', async () => {
    const { result } = renderHook(() => useAsyncAction<string>());

    let resolvePromise!: (v: string) => void;
    const pending = new Promise<string>((res) => {
      resolvePromise = res;
    });

    // Start execution (don't await yet)
    act(() => {
      void result.current.execute(() => pending);
    });

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

    await act(async () => {
      resolvePromise('done');
    });

    expect(result.current.isLoading).toBe(false);
  });

  it('returns the resolved value on success', async () => {
    const { result } = renderHook(() => useAsyncAction<number>());

    let returnValue: number | null = null;
    await act(async () => {
      returnValue = await result.current.execute(() => Promise.resolve(42));
    });

    expect(returnValue).toBe(42);
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('sets error message on failure', async () => {
    const { result } = renderHook(() => useAsyncAction<void>());

    await act(async () => {
      await result.current.execute(() => Promise.reject(new Error('Network error')));
    });

    expect(result.current.error).toBe('Network error');
    expect(result.current.isLoading).toBe(false);
  });

  it('returns null on failure', async () => {
    const { result } = renderHook(() => useAsyncAction<string>());

    let returnValue: string | null = 'initial';
    await act(async () => {
      returnValue = await result.current.execute(() => Promise.reject(new Error('fail')));
    });

    expect(returnValue).toBeNull();
  });

  it('uses fallback message for non-Error rejections', async () => {
    const { result } = renderHook(() => useAsyncAction<void>());

    await act(async () => {
      await result.current.execute(() => Promise.reject('string error'));
    });

    expect(result.current.error).toBe('An unexpected error occurred. Try again.');
  });

  it('clears error with clearError()', async () => {
    const { result } = renderHook(() => useAsyncAction<void>());

    await act(async () => {
      await result.current.execute(() => Promise.reject(new Error('oops')));
    });

    expect(result.current.error).toBe('oops');

    act(() => {
      result.current.clearError();
    });

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

  it('clears previous error on new execution', async () => {
    const { result } = renderHook(() => useAsyncAction<void>());

    await act(async () => {
      await result.current.execute(() => Promise.reject(new Error('first error')));
    });

    expect(result.current.error).toBe('first error');

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

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