import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
  copyTemplate,
  createTemplate,
  createTemplateGroup,
  deleteTemplate,
  deleteTemplateGroup,
  getTemplate,
  getTemplateGroups,
  getTemplateItems,
  getTemplates,
  replaceTemplate,
  type ReplaceTemplateRequest
} from './templates';

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: ReplaceTemplateRequest = {
  name: 'Weekdays',
  items: [{ blockId: 10, startTime: '06:00:00' }]
};

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

  it('getTemplateGroups fetches all template groups', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([{ id: 1, name: 'Prime', templateCount: 2 }]));
    await expect(getTemplateGroups()).resolves.toHaveLength(1);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/groups', expect.objectContaining({ method: 'GET' }));
  });

  it('createTemplateGroup POSTs the name', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3, name: 'Prime', templateCount: 0 }, 201));
    await createTemplateGroup({ name: 'Prime' });
    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe('/api/v1/templates/groups');
    expect(init).toMatchObject({ method: 'POST' });
    expect(JSON.parse(String(init?.body)).name).toBe('Prime');
  });

  it('deleteTemplateGroup DELETEs by id', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent());
    await expect(deleteTemplateGroup(3)).resolves.toBeUndefined();
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/groups/3', expect.objectContaining({ method: 'DELETE' }));
  });

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

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

  it('createTemplate POSTs group id and name', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 8 }, 201));
    await createTemplate({ templateGroupId: 2, name: 'Weekdays' });
    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe('/api/v1/templates');
    expect(init).toMatchObject({ method: 'POST' });
    expect(JSON.parse(String(init?.body))).toMatchObject({ templateGroupId: 2, name: 'Weekdays' });
  });

  it('deleteTemplate DELETEs by id', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent());
    await deleteTemplate(4);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/4', expect.objectContaining({ method: 'DELETE' }));
  });

  it('getTemplateItems fetches items for a template', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
    await getTemplateItems(4);
    expect(fetchMock).toHaveBeenCalledWith('/api/v1/templates/4/items', expect.objectContaining({ method: 'GET' }));
  });

  it('replaceTemplate PUTs the full template body', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4, items: [] }));
    await replaceTemplate(4, sampleReplace);
    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe('/api/v1/templates/4');
    expect(init).toMatchObject({ method: 'PUT' });
    const body = JSON.parse(String(init?.body));
    expect(body.name).toBe('Weekdays');
    expect(body.items).toHaveLength(1);
    expect(body.items[0]).toMatchObject({ blockId: 10, startTime: '06:00:00' });
  });

  it('copyTemplate POSTs the target group and name to the copy route', async () => {
    const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 9 }, 201));
    await copyTemplate(4, { templateGroupId: 3, name: 'Weekdays Copy' });
    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe('/api/v1/templates/4/copy');
    expect(init).toMatchObject({ method: 'POST' });
    expect(JSON.parse(String(init?.body))).toMatchObject({ templateGroupId: 3, name: 'Weekdays Copy' });
  });
});
