import { describe, it, expect, vi, beforeEach } from 'vitest';

const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({
  mockGet: vi.fn(),
  mockPost: vi.fn(),
  mockPatch: vi.fn(),
  mockDelete: vi.fn(),
}));

vi.mock('../../src/services/api-client', () => ({
  apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete },
}));

import {
  listRecipes,
  getRecipe,
  createRecipe,
  updateRecipe,
  deleteRecipe,
  scaleRecipe,
  importRecipeFromText,
  importRecipeFromUrl,
  listRecipesByProduct,
} from '../../src/services/recipes';

beforeEach(() => vi.clearAllMocks());

describe('recipes service', () => {
  it('listRecipes with no query', async () => {
    mockGet.mockResolvedValue({ data: [] });
    await listRecipes('hh1');
    expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes');
  });

  it('listRecipes builds query string', async () => {
    mockGet.mockResolvedValue({ data: [] });
    await listRecipes('hh1', {
      q: 'pasta',
      tags: 'italian',
      cuisine: 'Italian',
      maxCalories: 500,
      isFavorite: true,
      cursor: 'cur1',
      limit: 10,
    });
    const url = mockGet.mock.calls[0][0] as string;
    expect(url).toContain('q=pasta');
    expect(url).toContain('tags=italian');
    expect(url).toContain('cuisine=Italian');
    expect(url).toContain('maxCalories=500');
    expect(url).toContain('isFavorite=true');
    expect(url).toContain('cursor=cur1');
    expect(url).toContain('limit=10');
  });

  it('getRecipe calls GET', async () => {
    mockGet.mockResolvedValue({ _id: 'r1' });
    await getRecipe('hh1', 'r1');
    expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes/r1');
  });

  it('createRecipe calls POST', async () => {
    mockPost.mockResolvedValue({ _id: 'r1' });
    const data = { name: 'Pasta' };
    await createRecipe('hh1', data as never);
    expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes', data);
  });

  it('updateRecipe calls PATCH', async () => {
    mockPatch.mockResolvedValue({ _id: 'r1' });
    await updateRecipe('hh1', 'r1', { name: 'Updated' } as never);
    expect(mockPatch).toHaveBeenCalledWith('/households/hh1/recipes/r1', { name: 'Updated' });
  });

  it('deleteRecipe calls DELETE', async () => {
    mockDelete.mockResolvedValue(undefined);
    await deleteRecipe('hh1', 'r1');
    expect(mockDelete).toHaveBeenCalledWith('/households/hh1/recipes/r1');
  });

  it('scaleRecipe calls POST', async () => {
    mockPost.mockResolvedValue({ _id: 'r1' });
    await scaleRecipe('hh1', 'r1', { targetServings: 8 });
    expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/r1/scale', {
      targetServings: 8,
    });
  });

  it('importRecipeFromText calls POST', async () => {
    mockPost.mockResolvedValue({ available: false });
    await importRecipeFromText('hh1', { text: 'recipe text' } as never);
    expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/import-text', {
      text: 'recipe text',
    });
  });

  it('importRecipeFromUrl calls POST', async () => {
    mockPost.mockResolvedValue({ available: false });
    await importRecipeFromUrl('hh1', { url: 'http://example.com' } as never);
    expect(mockPost).toHaveBeenCalledWith('/households/hh1/recipes/import-url', {
      url: 'http://example.com',
    });
  });

  it('listRecipesByProduct with no query', async () => {
    mockGet.mockResolvedValue({ data: [] });
    await listRecipesByProduct('hh1', 'p1');
    expect(mockGet).toHaveBeenCalledWith('/households/hh1/recipes/by-product/p1');
  });

  it('listRecipesByProduct with query', async () => {
    mockGet.mockResolvedValue({ data: [] });
    await listRecipesByProduct('hh1', 'p1', { cursor: 'c1', limit: 5 });
    const url = mockGet.mock.calls[0][0] as string;
    expect(url).toContain('cursor=c1');
    expect(url).toContain('limit=5');
  });
});
