import { renderHook } from '@testing-library/react';
import { act } from 'react';
import useVideoControls from '../useVideoControls';

describe('useVideoControls', () => {
  let mockVideoElement: any;
  let mockVideoRef: any;

  beforeEach(() => {
    mockVideoElement = {
      load: jest.fn(),
      play: jest.fn().mockResolvedValue(undefined),
      pause: jest.fn(),
      muted: true,
      addEventListener: jest.fn(),
      removeEventListener: jest.fn(),
    };

    mockVideoRef = { current: mockVideoElement };

    jest.clearAllMocks();
  });

  // ─── Initial state ────────────────────────────────────────────────────────

  it('initializes with isPaused as true when autoplay is not set', () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));
    expect(result.current.isPaused).toBe(true);
  });

  it('initializes with isMuted as true', () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));
    expect(result.current.isMuted).toBe(true);
  });

  it('calls video.load() on mount', () => {
    renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));
    expect(mockVideoElement.load).toHaveBeenCalledTimes(1);
  });

  // ─── URL change ───────────────────────────────────────────────────────────

  it('calls video.load() again when the URL changes', () => {
    const { rerender } = renderHook(({ url }) => useVideoControls(mockVideoRef, url), {
      initialProps: { url: 'video1.mp4' },
    });

    act(() => { rerender({ url: 'video2.mp4' }); });

    // load is called once on mount and once when the URL changes
    expect(mockVideoElement.load).toHaveBeenCalledTimes(2);
  });

  it('sets isPaused back to true when the URL changes without autoplay', () => {
    const { result, rerender } = renderHook(({ url }) => useVideoControls(mockVideoRef, url), {
      initialProps: { url: 'video1.mp4' },
    });

    // Play first
    act(() => void result.current.playVideo());
    act(() => { rerender({ url: 'video2.mp4' }); });

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

  // ─── Autoplay ─────────────────────────────────────────────────────────────

  it('registers a loadeddata listener when autoplay is true', () => {
    renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true));
    expect(mockVideoElement.addEventListener).toHaveBeenCalledWith(
      'loadeddata',
      expect.any(Function),
      { once: true },
    );
  });

  it('plays and sets isPaused to false when loadeddata fires with autoplay', async () => {
    renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true));

    const [, loadedCallback] = mockVideoElement.addEventListener.mock.calls.find(
      ([event]: [string]) => event === 'loadeddata',
    );

    await act(async () => { await loadedCallback(); });

    expect(mockVideoElement.play).toHaveBeenCalled();
  });

  it('removes the loadeddata listener on unmount with autoplay', () => {
    const { unmount } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', true));
    unmount();
    expect(mockVideoElement.removeEventListener).toHaveBeenCalledWith(
      'loadeddata',
      expect.any(Function),
    );
  });

  it('does not register a loadeddata listener when autoplay is false', () => {
    renderHook(() => useVideoControls(mockVideoRef, 'test.mp4', false));
    const loadedCalls = mockVideoElement.addEventListener.mock.calls.filter(
      ([event]: [string]) => event === 'loadeddata',
    );
    expect(loadedCalls).toHaveLength(0);
  });

  // ─── playVideo ────────────────────────────────────────────────────────────

  it('calls video.play() and sets isPaused to false', async () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));

    await act(async () => { await result.current.playVideo(); });

    expect(mockVideoElement.play).toHaveBeenCalled();
    expect(result.current.isPaused).toBe(false);
  });

  it('sets isPaused to true and throws when play() rejects', async () => {
    mockVideoElement.play.mockRejectedValueOnce(new Error('NotAllowedError'));
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));

    await expect(
      act(async () => { await result.current.playVideo(); }),
    ).rejects.toThrow('Error playing video. Please try again later.');

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

  // ─── pauseVideo ───────────────────────────────────────────────────────────

  it('calls video.pause() and sets isPaused to true', () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));

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

    expect(mockVideoElement.pause).toHaveBeenCalled();
    expect(result.current.isPaused).toBe(true);
  });

  // ─── toggleMute ───────────────────────────────────────────────────────────

  it('toggleMute flips isMuted from true to false and updates video.muted', () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));

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

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

    expect(result.current.isMuted).toBe(false);
    expect(mockVideoElement.muted).toBe(false);
  });

  it('toggleMute flips isMuted back to true on second call', () => {
    const { result } = renderHook(() => useVideoControls(mockVideoRef, 'test.mp4'));

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

    expect(result.current.isMuted).toBe(true);
    expect(mockVideoElement.muted).toBe(true);
  });

  it('restores the previous unmuted preference after a force-muted post', () => {
    const { result, rerender } = renderHook(
      ({ forcedMuted }) => useVideoControls(mockVideoRef, 'test.mp4', false, forcedMuted),
      { initialProps: { forcedMuted: false } },
    );

    act(() => { result.current.toggleMute(); });
    expect(result.current.isMuted).toBe(false);

    act(() => { rerender({ forcedMuted: true }); });
    expect(result.current.isMuted).toBe(true);
    expect(mockVideoElement.muted).toBe(true);

    act(() => { rerender({ forcedMuted: false }); });
    expect(result.current.isMuted).toBe(false);
    expect(mockVideoElement.muted).toBe(false);
  });

  it('toggleMute does nothing when videoRef.current is null', () => {
    const nullRef = { current: null };
    const { result } = renderHook(() => useVideoControls(nullRef as any, 'test.mp4'));

    // Should not throw
    act(() => { result.current.toggleMute(); });

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