import { renderHook, act, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  useRoutePreload,
  usePerformanceOptimization,
  useErrorHandler,
  useLoadingState,
} from './useRoutePreload';

describe('useRoutePreload additional hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('useRoutePreload', () => {
    it('should preload route with delay', async () => {
      const { result } = renderHook(() => useRoutePreload('/test-route', 100));

      expect(result.current.isPreloading).toBe(false);
      expect(result.current.isPreloaded).toBe(false);

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

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

      await act(async () => {
        vi.advanceTimersByTime(100);
        await waitFor(() => {
          expect(result.current.isPreloaded).toBe(true);
        });
      });

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

  describe('usePerformanceOptimization', () => {
    it('should return performance optimization hook', () => {
      const { result } = renderHook(() => usePerformanceOptimization());

      expect(result.current).toBeDefined();
      expect(result.current).toHaveProperty('isVisible');
      expect(result.current).toHaveProperty('optimizeForVisibility');
      expect(result.current).toHaveProperty('throttle');
      expect(result.current).toHaveProperty('debounce');
    });

    it('should detect visibility changes', () => {
      const { result } = renderHook(() => usePerformanceOptimization());

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

      Object.defineProperty(document, 'hidden', {
        writable: true,
        value: true,
      });

      document.dispatchEvent(new Event('visibilitychange'));

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

    it('should throttle function calls', () => {
      const { result } = renderHook(() => usePerformanceOptimization());
      const mockFn = vi.fn();
      const throttled = result.current.throttle(mockFn, 100);

      throttled();
      throttled();
      throttled();

      expect(mockFn).toHaveBeenCalledTimes(1);
    });

    it('should debounce function calls', async () => {
      const { result } = renderHook(() => usePerformanceOptimization());
      const mockFn = vi.fn();
      const debounced = result.current.debounce(mockFn, 100);

      debounced();
      debounced();
      debounced();

      expect(mockFn).not.toHaveBeenCalled();

      await act(async () => {
        vi.advanceTimersByTime(100);
      });

      expect(mockFn).toHaveBeenCalledTimes(1);
    });
  });

  describe('useErrorHandler', () => {
    it('should return error handler hook', () => {
      const { result } = renderHook(() => useErrorHandler());

      expect(result.current).toBeDefined();
      expect(result.current).toHaveProperty('error');
      expect(result.current).toHaveProperty('handleError');
      expect(result.current).toHaveProperty('clearError');
      expect(result.current).toHaveProperty('withErrorHandling');
    });

    it('should handle errors', () => {
      const { result } = renderHook(() => useErrorHandler());
      const testError = new Error('Test error');

      act(() => {
        result.current.handleError(testError);
      });

      expect(result.current.error).toEqual(testError);
    });

    it('should clear errors', () => {
      const { result } = renderHook(() => useErrorHandler());
      const testError = new Error('Test error');

      act(() => {
        result.current.handleError(testError);
      });

      expect(result.current.error).toEqual(testError);

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

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

    it('should wrap function with error handling', () => {
      const { result } = renderHook(() => useErrorHandler());
      const testFn = vi.fn(() => {
        throw new Error('Test error');
      });

      const wrappedFn = result.current.withErrorHandling(testFn);

      expect(() => wrappedFn()).toThrow();
      expect(result.current.error).toBeDefined();
    });
  });

  describe('useLoadingState', () => {
    it('should return loading state hook', () => {
      const { result } = renderHook(() => useLoadingState());

      expect(result.current).toBeDefined();
      expect(result.current).toHaveProperty('isLoading');
      expect(result.current).toHaveProperty('setLoading');
      expect(result.current).toHaveProperty('setLoadingFor');
      expect(result.current).toHaveProperty('isLoadingFor');
      expect(result.current).toHaveProperty('withLoading');
    });

    it('should set loading state', () => {
      const { result } = renderHook(() => useLoadingState());

      act(() => {
        result.current.setLoading(true);
      });

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

    it('should set loading state for specific key', () => {
      const { result } = renderHook(() => useLoadingState());

      act(() => {
        result.current.setLoadingFor('test-key', true);
      });

      expect(result.current.isLoadingFor('test-key')).toBe(true);
    });

    it('should handle async operations with loading', async () => {
      const { result } = renderHook(() => useLoadingState());
      const asyncFn = vi.fn(async () => {
        await new Promise((resolve) => setTimeout(resolve, 100));
        return 'result';
      });

      const promise = act(async () => {
        return await result.current.withLoading(asyncFn);
      });

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

      await act(async () => {
        vi.advanceTimersByTime(100);
        await promise;
      });

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