import { act, renderHook } from '@testing-library/react-native';

import { audioFocus } from '@/core/audio/audio-focus';
import { usePlayer } from '@/features/voice/hooks/use-player';

const mockUseAudioPlayer = jest.fn();
const mockUseAudioPlayerStatus = jest.fn();

jest.mock('expo-audio', () => ({
  useAudioPlayer: (...args: unknown[]) => mockUseAudioPlayer(...args),
  useAudioPlayerStatus: (...args: unknown[]) =>
    mockUseAudioPlayerStatus(...args),
}));

jest.mock('@/core/audio/audio-focus', () => ({
  audioFocus: {
    acquireForPlayback: jest.fn(),
    releaseIfOwnedBy: jest.fn(),
    onOwnerChange: jest.fn(() => jest.fn()),
  },
}));

describe('usePlayer', () => {
  beforeEach(() => {
    mockUseAudioPlayer.mockReset();
    mockUseAudioPlayerStatus.mockReset();

    mockUseAudioPlayer.mockReturnValue({
      pause: jest.fn(),
      play: jest.fn(),
    });
    mockUseAudioPlayerStatus.mockReturnValue({
      isLoaded: false,
      playing: false,
      currentTime: 0,
      duration: 0,
    });
    jest.mocked(audioFocus.acquireForPlayback).mockResolvedValue(true);
    jest.mocked(audioFocus.releaseIfOwnedBy).mockResolvedValue(undefined);
    jest.mocked(audioFocus.onOwnerChange).mockImplementation(() => jest.fn());
  });

  test('keeps the native audio session active while app-level audio focus owns teardown', () => {
    renderHook(() => usePlayer());

    expect(mockUseAudioPlayer).toHaveBeenCalledWith(
      null,
      expect.objectContaining({
        downloadFirst: false,
        keepAudioSessionActive: true,
      }),
    );
  });

  test('pausePlayback toggles playing→paused and invokes native pause', async () => {
    mockUseAudioPlayerStatus.mockReturnValue({
      isLoaded: true,
      playing: true,
      currentTime: 0.1,
      duration: 10,
    });
    const pause = jest.fn();
    const play = jest.fn();
    mockUseAudioPlayer.mockReturnValue({ pause, play });

    const { result } = renderHook(() => usePlayer());

    await act(async () => {
      await result.current.enqueueExclusive({
        uri: 'file:///fixture.mp3',
        kind: 'voice',
      });
    });

    expect(result.current.status).toBe('playing');

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

    expect(pause).toHaveBeenCalled();
    expect(result.current.status).toBe('paused');
  });

  test('resumePlayback toggles paused→playing and invokes native play', async () => {
    mockUseAudioPlayerStatus.mockReturnValue({
      isLoaded: true,
      playing: false,
      currentTime: 0.1,
      duration: 10,
    });
    const pause = jest.fn();
    const play = jest.fn();
    mockUseAudioPlayer.mockReturnValue({ pause, play });

    const { result } = renderHook(() => usePlayer());

    await act(async () => {
      await result.current.enqueueExclusive({
        uri: 'file:///fixture.mp3',
        kind: 'voice',
      });
    });

    act(() => {
      result.current.pausePlayback();
    });
    expect(result.current.status).toBe('paused');

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

    expect(play).toHaveBeenCalled();
    expect(result.current.status).toBe('playing');
  });

  test('pausePlayback is a no-op while idle', async () => {
    const pause = jest.fn();
    mockUseAudioPlayer.mockReturnValue({ pause, play: jest.fn() });

    const { result } = renderHook(() => usePlayer());

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

    expect(pause).not.toHaveBeenCalled();
    expect(result.current.status).toBe('idle');
  });

  test('retries queued audio after acquire fails once then audio focus frees', async () => {
    const acquire = jest.mocked(audioFocus.acquireForPlayback);
    acquire.mockResolvedValueOnce(false).mockResolvedValue(true);

    let ownerListener: ((owner: null | string) => void) | undefined;
    jest.mocked(audioFocus.onOwnerChange).mockImplementation((cb) => {
      ownerListener = cb as (owner: null | string) => void;
      return jest.fn();
    });

    mockUseAudioPlayerStatus.mockReturnValue({
      isLoaded: true,
      playing: false,
      currentTime: 0,
      duration: 10,
    });
    const play = jest.fn();
    mockUseAudioPlayer.mockReturnValue({ pause: jest.fn(), play });

    const { result } = renderHook(() => usePlayer());

    await act(async () => {
      await result.current.enqueue({
        uri: 'file:///queued.mp3',
        kind: 'tts_auto',
      });
    });

    expect(acquire).toHaveBeenCalledTimes(1);
    expect(result.current.status).toBe('idle');

    await act(async () => {
      ownerListener?.(null);
    });

    expect(acquire).toHaveBeenCalledTimes(2);
    expect(play).toHaveBeenCalled();
  });
});
