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

describe('useKeyboardShortcuts', () => {
  const mockPlayer: PlayerHook = {
    currentTrack: null,
    isPlaying: false,
    currentTime: 30,
    duration: 180,
    volume: 50,
    muted: false,
    queue: [],
    currentIndex: -1,
    repeat: 'off',
    shuffle: false,
    play: vi.fn(),
    pause: vi.fn(),
    resume: vi.fn(),
    stop: vi.fn(),
    next: vi.fn(),
    previous: vi.fn(),
    seek: vi.fn(),
    setVolume: vi.fn(),
    toggleMute: vi.fn(),
    toggleShuffle: vi.fn(),
    setRepeat: vi.fn(),
    addToQueue: vi.fn(),
    clearQueue: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

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

  it('should add keydown event listener when enabled', () => {
    const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
    const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');

    const { unmount } = renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true }),
    );

    expect(addEventListenerSpy).toHaveBeenCalledWith(
      'keydown',
      expect.any(Function),
    );

    unmount();

    expect(removeEventListenerSpy).toHaveBeenCalledWith(
      'keydown',
      expect.any(Function),
    );
  });

  it('should not add event listener when disabled', () => {
    const addEventListenerSpy = vi.spyOn(window, 'addEventListener');

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: false }));

    expect(addEventListenerSpy).not.toHaveBeenCalled();
  });

  it('should handle Space key for play/pause when paused', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).toHaveBeenCalled();
    expect(mockPlayer.pause).not.toHaveBeenCalled();
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should handle Space key for play/pause when playing', () => {
    const playingPlayer = { ...mockPlayer, isPlaying: true };
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() => useKeyboardShortcuts(playingPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(playingPlayer.pause).toHaveBeenCalled();
    expect(playingPlayer.resume).not.toHaveBeenCalled();
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should handle ArrowLeft key for seek backward', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowLeft',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, seekStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.seek).toHaveBeenCalledWith(25); // 30 - 5
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should handle ArrowRight key for seek forward', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowRight',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, seekStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.seek).toHaveBeenCalledWith(35); // 30 + 5
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should not seek below 0', () => {
    const playerAtStart = { ...mockPlayer, currentTime: 2 };
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowLeft',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(playerAtStart, { enabled: true, seekStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(playerAtStart.seek).toHaveBeenCalledWith(0);
  });

  it('should not seek beyond duration', () => {
    const playerNearEnd = { ...mockPlayer, currentTime: 178, duration: 180 };
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowRight',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(playerNearEnd, { enabled: true, seekStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(playerNearEnd.seek).toHaveBeenCalledWith(180);
  });

  it('should handle ArrowUp key for volume up', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowUp',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, volumeStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.setVolume).toHaveBeenCalledWith(55); // 50 + 5
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should handle ArrowDown key for volume down', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowDown',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, volumeStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.setVolume).toHaveBeenCalledWith(45); // 50 - 5
    expect(preventDefaultSpy).toHaveBeenCalled();
  });

  it('should not increase volume beyond 100', () => {
    const playerAtMaxVolume = { ...mockPlayer, volume: 98 };
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowUp',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(playerAtMaxVolume, { enabled: true, volumeStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(playerAtMaxVolume.setVolume).toHaveBeenCalledWith(100);
  });

  it('should not decrease volume below 0', () => {
    const playerAtMinVolume = { ...mockPlayer, volume: 2 };
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowDown',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(playerAtMinVolume, { enabled: true, volumeStep: 5 }),
    );

    window.dispatchEvent(mockEvent);

    expect(playerAtMinVolume.setVolume).toHaveBeenCalledWith(0);
  });

  it('should use custom seekStep', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowRight',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, seekStep: 10 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.seek).toHaveBeenCalledWith(40); // 30 + 10
  });

  it('should use custom volumeStep', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'ArrowUp',
      bubbles: true,
    });

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, { enabled: true, volumeStep: 10 }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.setVolume).toHaveBeenCalledWith(60); // 50 + 10
  });

  it('should not prevent default when preventDefault is false', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    const preventDefaultSpy = vi.spyOn(mockEvent, 'preventDefault');

    renderHook(() =>
      useKeyboardShortcuts(mockPlayer, {
        enabled: true,
        preventDefault: false,
      }),
    );

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).toHaveBeenCalled();
    expect(preventDefaultSpy).not.toHaveBeenCalled();
  });

  it('should ignore keys when typing in input', () => {
    const input = document.createElement('input');
    document.body.appendChild(input);
    input.focus();

    // Create event with input as target
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    Object.defineProperty(mockEvent, 'target', {
      value: input,
      writable: false,
    });

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).not.toHaveBeenCalled();
    expect(mockPlayer.pause).not.toHaveBeenCalled();

    document.body.removeChild(input);
  });

  it('should ignore keys when typing in textarea', () => {
    const textarea = document.createElement('textarea');
    document.body.appendChild(textarea);
    textarea.focus();

    // Create event with textarea as target
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    Object.defineProperty(mockEvent, 'target', {
      value: textarea,
      writable: false,
    });

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).not.toHaveBeenCalled();
    expect(mockPlayer.pause).not.toHaveBeenCalled();

    document.body.removeChild(textarea);
  });

  it('should ignore keys when contentEditable is true', () => {
    const div = document.createElement('div');
    div.contentEditable = 'true';
    document.body.appendChild(div);
    div.focus();

    // Create event with div as target
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'Space',
      bubbles: true,
    });
    Object.defineProperty(mockEvent, 'target', {
      value: div,
      writable: false,
    });
    // Ensure isContentEditable is properly set
    Object.defineProperty(div, 'isContentEditable', {
      value: true,
      writable: false,
      configurable: true,
    });

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).not.toHaveBeenCalled();
    expect(mockPlayer.pause).not.toHaveBeenCalled();

    document.body.removeChild(div);
  });

  it('should ignore unknown keys', () => {
    const mockEvent = new KeyboardEvent('keydown', {
      code: 'KeyA',
      bubbles: true,
    });

    renderHook(() => useKeyboardShortcuts(mockPlayer, { enabled: true }));

    window.dispatchEvent(mockEvent);

    expect(mockPlayer.resume).not.toHaveBeenCalled();
    expect(mockPlayer.pause).not.toHaveBeenCalled();
    expect(mockPlayer.seek).not.toHaveBeenCalled();
    expect(mockPlayer.setVolume).not.toHaveBeenCalled();
  });
});
