import { afterEach, describe, expect, test, vi } from 'vitest';
import {
  listPlaylists,
  getPlaylist,
  createPlaylist,
  reorderPlaylist,
  removePlaylistTrack
} from './playlists';

function stubFetch(status: number, body: unknown, init: Partial<Response> = {}) {
  const res = new Response(
    body === null ? null : JSON.stringify(body),
    { status, headers: { 'Content-Type': 'application/json' }, ...init }
  );
  const spy = vi.fn().mockResolvedValue(res);
  vi.stubGlobal('fetch', spy);
  return spy;
}

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

describe('playlists API helper', () => {
  test('listPlaylists GETs /api/playlists with default kind=user', async () => {
    const spy = stubFetch(200, { owned: [], public: [] });
    const r = await listPlaylists();
    expect(r.owned).toEqual([]);
    expect(r.public).toEqual([]);
    const call = spy.mock.calls[0];
    expect(call[0]).toBe('/api/playlists?kind=user');
    // GET is fetch's default method; api.get omits init.method entirely.
    expect((call[1] as RequestInit | undefined)?.method ?? 'GET').toBe('GET');
  });

  test('listPlaylists passes kind=system through the query string', async () => {
    const spy = stubFetch(200, { owned: [], public: [] });
    await listPlaylists('system');
    const call = spy.mock.calls[0];
    expect(call[0]).toBe('/api/playlists?kind=system');
  });

  test('createPlaylist POSTs JSON body', async () => {
    const spy = stubFetch(200, { id: 'p1', name: 'Test' });
    const r = await createPlaylist({ name: 'Test' });
    expect(r.id).toBe('p1');
    const call = spy.mock.calls[0];
    expect((call[1] as RequestInit).method).toBe('POST');
    expect(JSON.parse((call[1] as RequestInit).body as string)).toEqual({ name: 'Test' });
  });

  test('reorderPlaylist PUTs ordered_positions', async () => {
    const spy = stubFetch(200, { id: 'p1', tracks: [] });
    await reorderPlaylist('p1', [2, 1, 0]);
    const call = spy.mock.calls[0];
    expect(call[0]).toBe('/api/playlists/p1/tracks');
    expect((call[1] as RequestInit).method).toBe('PUT');
    expect(JSON.parse((call[1] as RequestInit).body as string)).toEqual({
      ordered_positions: [2, 1, 0]
    });
  });

  test('removePlaylistTrack DELETEs by position', async () => {
    const spy = stubFetch(200, { id: 'p1', tracks: [] });
    await removePlaylistTrack('p1', 3);
    const call = spy.mock.calls[0];
    expect(call[0]).toBe('/api/playlists/p1/tracks/3');
    expect((call[1] as RequestInit).method).toBe('DELETE');
  });

  test('not_found surfaces as ApiError', async () => {
    stubFetch(404, { error: 'not_found' });
    await expect(getPlaylist('missing')).rejects.toMatchObject({ code: 'not_found' });
  });
});
