/**
 * Tests for HistoryContext
 */

import { describe, it, expect, vi } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { HistoryProvider, useHistory, useHistoryKeyboard } from '@/contexts/HistoryContext';
import React from 'react';

// Wrapper component for testing
const createWrapper = (initialState?: any, onStateChange?: (state: any) => void) => {
  return ({ children }: { children: React.ReactNode }) => (
    <HistoryProvider initialState={initialState} onStateChange={onStateChange}>
      {children}
    </HistoryProvider>
  );
};

describe('HistoryContext', () => {
  it('should initialize with no history', () => {
    const wrapper = createWrapper();
    const { result } = renderHook(() => useHistory(), { wrapper });

    expect(result.current.canUndo).toBe(false);
    expect(result.current.canRedo).toBe(false);
    expect(result.current.historySize).toBe(0);
    expect(result.current.currentEntry).toBeNull();
  });

  it('should initialize with initial state', () => {
    const initialState = { count: 0 };
    const wrapper = createWrapper(initialState);
    const { result } = renderHook(() => useHistory(), { wrapper });

    expect(result.current.currentEntry).not.toBeNull();
    expect(result.current.currentEntry?.state).toEqual(initialState);
    expect(result.current.currentEntry?.action).toBe('Initial state');
    expect(result.current.historySize).toBe(1);
  });

  it('should push new state to history', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 1 });
    expect(result.current.currentEntry?.action).toBe('Increment');
    expect(result.current.canUndo).toBe(true);
    expect(result.current.canRedo).toBe(false);
    expect(result.current.historySize).toBe(2);
  });

  it('should undo to previous state', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
      result.current.pushHistory({ count: 2 }, 'Increment');
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 2 });

    let previousState: any;
    act(() => {
      previousState = result.current.undo();
    });

    expect(previousState).toEqual({ count: 1 });
    expect(result.current.currentEntry?.state).toEqual({ count: 1 });
    expect(result.current.canUndo).toBe(true);
    expect(result.current.canRedo).toBe(true);
  });

  it('should redo to next state', async () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
    });
    
    act(() => {
      result.current.pushHistory({ count: 2 }, 'Increment');
    });
    
    act(() => {
      result.current.undo();
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 1 });

    let nextState: any;
    act(() => {
      nextState = result.current.redo();
    });

    expect(nextState).toEqual({ count: 2 });
    expect(result.current.currentEntry?.state).toEqual({ count: 2 });
    expect(result.current.canUndo).toBe(true);
    expect(result.current.canRedo).toBe(false);
  });

  it('should return null when undoing with no past', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    let previousState: any;
    act(() => {
      previousState = result.current.undo();
    });

    expect(previousState).toBeNull();
    expect(result.current.canUndo).toBe(false);
  });

  it('should return null when redoing with no future', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    let nextState: any;
    act(() => {
      nextState = result.current.redo();
    });

    expect(nextState).toBeNull();
    expect(result.current.canRedo).toBe(false);
  });

  it('should clear future when new action is pushed', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
    });
    
    act(() => {
      result.current.pushHistory({ count: 2 }, 'Increment');
    });
    
    act(() => {
      result.current.undo();
    });

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

    act(() => {
      result.current.pushHistory({ count: 10 }, 'Set to 10');
    });

    expect(result.current.canRedo).toBe(false);
    expect(result.current.currentEntry?.state).toEqual({ count: 10 });
  });

  it('should clear all history', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
      result.current.pushHistory({ count: 2 }, 'Increment');
    });

    expect(result.current.historySize).toBe(3);

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

    expect(result.current.historySize).toBe(0);
    expect(result.current.canUndo).toBe(false);
    expect(result.current.canRedo).toBe(false);
    expect(result.current.currentEntry).toBeNull();
  });

  it('should jump to specific entry', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
      result.current.pushHistory({ count: 2 }, 'Increment');
      result.current.pushHistory({ count: 3 }, 'Increment');
    });

    let targetState: any;
    act(() => {
      targetState = result.current.jumpToEntry(1); // Jump to count: 1
    });

    expect(targetState).toEqual({ count: 1 });
    expect(result.current.currentEntry?.state).toEqual({ count: 1 });
    expect(result.current.canUndo).toBe(true);
    expect(result.current.canRedo).toBe(true);
  });

  it('should return null when jumping to invalid index', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    let targetState: any;
    act(() => {
      targetState = result.current.jumpToEntry(-1);
    });

    expect(targetState).toBeNull();

    act(() => {
      targetState = result.current.jumpToEntry(100);
    });

    expect(targetState).toBeNull();
  });

  it('should respect max history size', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <HistoryProvider initialState={{ count: 0 }} maxHistorySize={3}>
        {children}
      </HistoryProvider>
    );
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      for (let i = 1; i <= 10; i++) {
        result.current.pushHistory({ count: i }, `Set to ${i}`);
      }
    });

    // Max 3 past + 1 present = 4 total
    expect(result.current.historySize).toBeLessThanOrEqual(4);
  });

  it('should get past and future entries', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
    });
    
    act(() => {
      result.current.pushHistory({ count: 2 }, 'Increment');
    });
    
    act(() => {
      result.current.undo();
    });

    const pastEntries = result.current.getPastEntries();
    const futureEntries = result.current.getFutureEntries();

    // After undo: past=[count:0], present=[count:1], future=[count:2]
    expect(pastEntries.length).toBe(1); // Only initial state
    expect(futureEntries.length).toBe(1); // The undone state
    expect(pastEntries[0].state).toEqual({ count: 0 });
    expect(result.current.currentEntry?.state).toEqual({ count: 1 });
    expect(futureEntries[0].state).toEqual({ count: 2 });
  });

  it('should call onStateChange when state changes', () => {
    const onStateChange = vi.fn();
    const wrapper = createWrapper({ count: 0 }, onStateChange);
    const { result } = renderHook(() => useHistory(), { wrapper });

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
    });

    expect(onStateChange).toHaveBeenCalledWith({ count: 1 });
  });

  it('should throw error when useHistory is called outside provider', () => {
    // Suppress console.error for this test
    const consoleError = console.error;
    console.error = vi.fn();

    expect(() => {
      renderHook(() => useHistory());
    }).toThrow('useHistory must be used within a HistoryProvider');

    console.error = consoleError;
  });
});

describe('useHistoryKeyboard', () => {
  it('should handle keyboard shortcuts for undo/redo', () => {
    const wrapper = createWrapper({ count: 0 });
    const { result } = renderHook(
      () => {
        const history = useHistory();
        useHistoryKeyboard();
        return history;
      },
      { wrapper }
    );

    act(() => {
      result.current.pushHistory({ count: 1 }, 'Increment');
      result.current.pushHistory({ count: 2 }, 'Increment');
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 2 });

    // Simulate Ctrl+Z (undo)
    act(() => {
      const event = new KeyboardEvent('keydown', {
        key: 'z',
        ctrlKey: true,
        bubbles: true,
      });
      window.dispatchEvent(event);
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 1 });

    // Simulate Ctrl+Shift+Z (redo)
    act(() => {
      const event = new KeyboardEvent('keydown', {
        key: 'z',
        ctrlKey: true,
        shiftKey: true,
        bubbles: true,
      });
      window.dispatchEvent(event);
    });

    expect(result.current.currentEntry?.state).toEqual({ count: 2 });
  });
});
