import { renderHook, act, waitFor } from '@testing-library/react';
import { useNetworkStatus, useOptimisticUpdate } from '../../hooks/useNetworkStatus';

describe('useNetworkStatus', () => {
  let originalOnline: boolean;

  beforeEach(() => {
    originalOnline = navigator.onLine;
    Object.defineProperty(navigator, 'onLine', {
      writable: true,
      value: true,
    });
  });

  afterEach(() => {
    Object.defineProperty(navigator, 'onLine', {
      writable: true,
      value: originalOnline,
    });
  });

  it('should return initial online status', () => {
    const { result } = renderHook(() => useNetworkStatus());
    expect(result.current.isOnline).toBe(true);
  });

  it('should detect offline status', () => {
    Object.defineProperty(navigator, 'onLine', {
      writable: true,
      value: false,
    });

    const { result } = renderHook(() => useNetworkStatus());
    expect(result.current.isOnline).toBe(false);
  });

  it('should execute operation with retry on success', async () => {
    const { result } = renderHook(() => useNetworkStatus());
    const operation = jest.fn().mockResolvedValue('success');

    const response = await result.current.executeWithRetry(operation, {
      maxRetries: 3,
    });

    expect(operation).toHaveBeenCalledTimes(1);
    expect(response).toBe('success');
  });

  it('should retry on failure and eventually throw', async () => {
    const { result } = renderHook(() => useNetworkStatus());
    const operation = jest.fn().mockRejectedValue(new Error('Network error'));

    await expect(
      result.current.executeWithRetry(operation, {
        maxRetries: 2,
        retryDelay: 10,
      })
    ).rejects.toThrow('Network error');

    expect(operation).toHaveBeenCalledTimes(3); // initial + 2 retries
  });

  it('should call onRetry callback', async () => {
    const { result } = renderHook(() => useNetworkStatus());
    const onRetry = jest.fn();
    const operation = jest
      .fn()
      .mockRejectedValueOnce(new Error('error'))
      .mockResolvedValue('success');

    await result.current.executeWithRetry(operation, {
      maxRetries: 2,
      retryDelay: 10,
      onRetry,
    });

    expect(onRetry).toHaveBeenCalledWith(1);
  });

  it('should add operation to queue when offline', () => {
    Object.defineProperty(navigator, 'onLine', {
      writable: true,
      value: false,
    });

    const { result } = renderHook(() => useNetworkStatus());
    const operation = jest.fn().mockResolvedValue(undefined);

    act(() => {
      result.current.enqueueOperation(operation);
    });

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

describe('useOptimisticUpdate', () => {
  it('should execute optimistic update successfully', async () => {
    const { result } = renderHook(() => useOptimisticUpdate<string>());
    const optimistic = jest.fn();
    const serverOperation = jest.fn().mockResolvedValue('server result');
    const onSuccess = jest.fn();

    const response = await result.current.optimisticUpdate({
      optimistic,
      serverOperation,
      onSuccess,
    });

    expect(optimistic).toHaveBeenCalled();
    expect(serverOperation).toHaveBeenCalled();
    expect(onSuccess).toHaveBeenCalledWith('server result');
    expect(response.success).toBe(true);
    expect(response.data).toBe('server result');
  });

  it('should rollback on server error', async () => {
    const { result } = renderHook(() => useOptimisticUpdate<string>());
    const optimistic = jest.fn();
    const rollback = jest.fn();
    const serverOperation = jest.fn().mockRejectedValue(new Error('Server error'));
    const onError = jest.fn();

    const response = await result.current.optimisticUpdate({
      optimistic,
      onRollbackReady: (fn) => {
        // Store rollback function
        rollback.mockImplementation(fn);
      },
      serverOperation,
      onError,
    });

    expect(optimistic).toHaveBeenCalled();
    expect(serverOperation).toHaveBeenCalled();
    expect(onError).toHaveBeenCalledWith(new Error('Server error'));
    expect(response.success).toBe(false);
  });
});
