import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
  createDeco,
  createDecoGroup,
  deleteDeco,
  deleteDecoGroup,
  getDeco,
  getDecoGroups,
  getDecos,
  replaceDeco,
  type ReplaceDecoRequest
} from './decos';
import { searchArtists, searchMultiCollections } from './blocks';

function jsonResponse(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), { headers: { 'Content-Type': 'application/json' }, status });
}

function noContent(): Response {
  return new Response(null, { status: 204 });
}

const sampleReplace: ReplaceDecoRequest = {
  name: 'Movie Night',
  watermarkMode: 'Merge',
  watermarkIds: [1, 2],
  useWatermarkDuringFiller: false,
  graphicsElementsMode: 'Inherit',
  graphicsElementIds: [],
  useGraphicsElementsDuringFiller: false,
  breakContentMode: 'Override',
  breakContent: [
    {
      id: 0,
      collectionType: 'Playlist',
      collectionId: null,
      mediaItemId: null,
      multiCollectionId: null,
      smartCollectionId: null,
      playlistId: 9,
      placement: 'BlockStart'
    }
  ],
  defaultFillerMode: 'Inherit',
  defaultFillerCollectionType: 'Collection',
  defaultFillerCollectionId: null,
  defaultFillerMediaItemId: null,
  defaultFillerMultiCollectionId: null,
  defaultFillerSmartCollectionId: null,
  defaultFillerTrimToFit: false,
  deadAirFallbackMode: 'Inherit',
  deadAirFallbackCollectionType: 'Collection',
  deadAirFallbackCollectionId: null,
  deadAirFallbackMediaItemId: null,
  deadAirFallbackMultiCollectionId: null,
  deadAirFallbackSmartCollectionId: null
};

describe('decos api client', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  it('getDecoGroups fetches all deco groups', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([{ id: 1, name: 'Bumpers', decoCount: 0 }]));
    await expect(getDecoGroups()).resolves.toHaveLength(1);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/groups', expect.objectContaining({ method: 'GET' }));
  });

  it('createDecoGroup POSTs to the groups route', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 5, name: 'Bumpers', decoCount: 0 }, 201));
    await createDecoGroup({ name: 'Bumpers' });
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/groups', expect.objectContaining({ method: 'POST' }));
  });

  it('deleteDecoGroup DELETEs the group route', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent());
    await deleteDecoGroup(3);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/groups/3', expect.objectContaining({ method: 'DELETE' }));
  });

  it('getDecos fetches the flat list', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
    await getDecos();
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos', expect.objectContaining({ method: 'GET' }));
  });

  it('getDeco fetches a single deco', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4 }));
    await getDeco(4);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/4', expect.objectContaining({ method: 'GET' }));
  });

  it('createDeco POSTs to the decos route', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 8 }, 201));
    await createDeco({ decoGroupId: 2, name: 'Movie Night' });
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos', expect.objectContaining({ method: 'POST' }));
  });

  it('deleteDeco DELETEs the deco route', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent());
    await deleteDeco(4);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/4', expect.objectContaining({ method: 'DELETE' }));
  });

  it('replaceDeco PUTs the full state', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4 }));
    await replaceDeco(4, sampleReplace);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/decos/4', expect.objectContaining({ method: 'PUT' }));
    const init = fetchMock.mock.calls[0][1];
    const sent = JSON.parse(String(init?.body));
    expect(sent.breakContent[0].playlistId).toBe(9);
  });

  it('searchArtists and searchMultiCollections hit their picker routes', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockImplementation(() => Promise.resolve(jsonResponse([])));
    await searchArtists('bowie');
    await searchMultiCollections('mix');
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/search/artists?query=bowie', expect.objectContaining({ method: 'GET' }));
    expect(fetchMock).toHaveBeenCalledWith(
      '/api/v1/search/multi-collections?query=mix',
      expect.objectContaining({ method: 'GET' })
    );
  });
});
