import { renderHook } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

import { router } from '@inertiajs/react';

import { usePolling } from './usePolling';

// Mock Inertia router
vi.mock('@inertiajs/react', () => ({
  router: {
    reload: vi.fn(),
  },
}));

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

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

  // ============================================
  // Initial state tests
  // ============================================

  describe('initial state', () => {
    it('does not start polling when shouldPoll is false', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(false, 1000, ['job']));

      vi.advanceTimersByTime(2000);

      expect(router.reload).not.toHaveBeenCalled();
      vi.useRealTimers();
    });

    it('starts polling when shouldPoll is true', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 1000, ['job']));

      vi.advanceTimersByTime(1000);

      expect(router.reload).toHaveBeenCalledTimes(1);
      expect(router.reload).toHaveBeenCalledWith({ only: ['job'] });
      vi.useRealTimers();
    });

    it('polls multiple times at correct intervals', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 1000, ['job', 'results']));

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(2);

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(3);

      expect(router.reload).toHaveBeenCalledWith({ only: ['job', 'results'] });
      vi.useRealTimers();
    });
  });

  // ============================================
  // Polling activation/deactivation tests
  // ============================================

  describe('polling activation and deactivation', () => {
    it('stops polling when shouldPoll changes to false', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ shouldPoll }) => usePolling(shouldPoll, 1000, ['job']), {
        initialProps: { shouldPoll: true },
      });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      rerender({ shouldPoll: false });

      vi.advanceTimersByTime(2000);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });

    it('resumes polling when shouldPoll changes back to true', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ shouldPoll }) => usePolling(shouldPoll, 1000, ['job']), {
        initialProps: { shouldPoll: true },
      });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      rerender({ shouldPoll: false });
      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      rerender({ shouldPoll: true });
      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(2);
      vi.useRealTimers();
    });

    it('clears old interval when shouldPoll toggles', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ shouldPoll }) => usePolling(shouldPoll, 1000, ['job']), {
        initialProps: { shouldPoll: true },
      });

      vi.advanceTimersByTime(500);

      rerender({ shouldPoll: false });
      vi.advanceTimersByTime(500);
      expect(router.reload).not.toHaveBeenCalled();

      rerender({ shouldPoll: true });
      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });
  });

  // ============================================
  // Interval changes tests
  // ============================================

  describe('interval changes', () => {
    it('updates polling interval when intervalMs changes', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ intervalMs }) => usePolling(true, intervalMs, ['job']), {
        initialProps: { intervalMs: 1000 },
      });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      rerender({ intervalMs: 500 });

      vi.advanceTimersByTime(500);
      expect(router.reload).toHaveBeenCalledTimes(2);

      vi.advanceTimersByTime(500);
      expect(router.reload).toHaveBeenCalledTimes(3);
      vi.useRealTimers();
    });

    it('clears old interval when intervalMs changes', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ intervalMs }) => usePolling(true, intervalMs, ['job']), {
        initialProps: { intervalMs: 1000 },
      });

      vi.advanceTimersByTime(500);

      rerender({ intervalMs: 2000 });

      vi.advanceTimersByTime(500);
      expect(router.reload).not.toHaveBeenCalled();

      vi.advanceTimersByTime(1500);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });
  });

  // ============================================
  // ReloadKeys changes tests
  // ============================================

  describe('reloadKeys changes', () => {
    it('uses updated reloadKeys for subsequent polls', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ reloadKeys }) => usePolling(true, 1000, reloadKeys), {
        initialProps: { reloadKeys: ['job'] },
      });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledWith({ only: ['job'] });

      rerender({ reloadKeys: ['job', 'results', 'status'] });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledWith({ only: ['job', 'results', 'status'] });
      vi.useRealTimers();
    });

    it('handles empty reloadKeys array', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 1000, []));

      vi.advanceTimersByTime(1000);

      expect(router.reload).toHaveBeenCalledWith({ only: [] });
      vi.useRealTimers();
    });

    it('handles single reloadKey', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 1000, ['singleKey']));

      vi.advanceTimersByTime(1000);

      expect(router.reload).toHaveBeenCalledWith({ only: ['singleKey'] });
      vi.useRealTimers();
    });

    it('handles multiple reloadKeys', () => {
      vi.useFakeTimers();
      const manyKeys = ['key1', 'key2', 'key3', 'key4', 'key5'];
      renderHook(() => usePolling(true, 1000, manyKeys));

      vi.advanceTimersByTime(1000);

      expect(router.reload).toHaveBeenCalledWith({ only: manyKeys });
      vi.useRealTimers();
    });
  });

  // ============================================
  // Cleanup tests
  // ============================================

  describe('cleanup', () => {
    it('clears interval on unmount when polling is active', () => {
      vi.useFakeTimers();
      const { unmount } = renderHook(() => usePolling(true, 1000, ['job']));

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      unmount();

      vi.advanceTimersByTime(2000);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });

    it('handles unmount when polling is not active', () => {
      vi.useFakeTimers();
      const { unmount } = renderHook(() => usePolling(false, 1000, ['job']));

      unmount();

      vi.advanceTimersByTime(2000);
      expect(router.reload).not.toHaveBeenCalled();
      vi.useRealTimers();
    });

    it('clears interval mid-cycle on unmount', () => {
      vi.useFakeTimers();
      const { unmount } = renderHook(() => usePolling(true, 1000, ['job']));

      vi.advanceTimersByTime(500);

      unmount();

      vi.advanceTimersByTime(500);
      expect(router.reload).not.toHaveBeenCalled();
      vi.useRealTimers();
    });
  });

  // ============================================
  // Edge cases tests
  // ============================================

  describe('edge cases', () => {
    it('handles very short interval (100ms)', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 100, ['job']));

      vi.advanceTimersByTime(100);
      expect(router.reload).toHaveBeenCalledTimes(1);

      vi.advanceTimersByTime(100);
      expect(router.reload).toHaveBeenCalledTimes(2);
      vi.useRealTimers();
    });

    it('handles long interval (10000ms)', () => {
      vi.useFakeTimers();
      renderHook(() => usePolling(true, 10000, ['job']));

      vi.advanceTimersByTime(9999);
      expect(router.reload).not.toHaveBeenCalled();

      vi.advanceTimersByTime(1);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });

    it('handles rapid shouldPoll toggling', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(({ shouldPoll }) => usePolling(shouldPoll, 1000, ['job']), {
        initialProps: { shouldPoll: true },
      });

      rerender({ shouldPoll: false });
      rerender({ shouldPoll: true });
      rerender({ shouldPoll: false });
      rerender({ shouldPoll: true });

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);
      vi.useRealTimers();
    });
  });

  // ============================================
  // Real-world scenario tests
  // ============================================

  describe('real-world scenarios', () => {
    it('simulates job polling lifecycle (pending -> processing -> completed)', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(
        ({ status }) => usePolling(['pending', 'processing'].includes(status), 1000, ['job']),
        { initialProps: { status: 'pending' } },
      );

      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(1);

      rerender({ status: 'processing' });
      vi.advanceTimersByTime(1000);
      expect(router.reload).toHaveBeenCalledTimes(2);

      rerender({ status: 'completed' });
      vi.advanceTimersByTime(2000);
      expect(router.reload).toHaveBeenCalledTimes(2);
      vi.useRealTimers();
    });

    it('simulates analysis run with multiple reload keys', () => {
      vi.useFakeTimers();
      const { rerender } = renderHook(
        ({ status }) =>
          usePolling(status === 'processing', 5000, ['analysisRun', 'findings', 'recommendations']),
        { initialProps: { status: 'pending' } },
      );

      rerender({ status: 'processing' });

      vi.advanceTimersByTime(5000);
      expect(router.reload).toHaveBeenCalledWith({
        only: ['analysisRun', 'findings', 'recommendations'],
      });

      vi.advanceTimersByTime(5000);
      expect(router.reload).toHaveBeenCalledTimes(2);

      rerender({ status: 'completed' });
      vi.advanceTimersByTime(5000);
      expect(router.reload).toHaveBeenCalledTimes(2);
      vi.useRealTimers();
    });
  });
});
