import { NotFoundException } from '@nestjs/common';
import { RecipesService } from './recipes.service';

describe('RecipesService IDOR security', () => {
  const prismaMock = {
    recipe: {
      findUnique: jest.fn(),
      findFirst: jest.fn(),
      findMany: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    },
    recipeIngredient: {
      findMany: jest.fn(),
      create: jest.fn(),
      deleteMany: jest.fn(),
    },
    product: {
      findMany: jest.fn(),
      findFirst: jest.fn(),
    },
    user: {
      findUnique: jest.fn(),
    },
    recipeShare: {
      findUnique: jest.fn(),
      create: jest.fn(),
      delete: jest.fn(),
    },
    $transaction: jest.fn(),
  };

  const aiServiceMock = {};
  const recipeMatchingServiceMock = {};

  const service = new RecipesService(prismaMock as any, aiServiceMock as any, recipeMatchingServiceMock as any);

  beforeEach(() => {
    jest.clearAllMocks();
  });

  const setRecipeAsOwnedByAnotherUser = () => {
    prismaMock.recipe.findUnique.mockResolvedValue({
      id: 1,
      ownerId: 100,
    });
  };

  it('findAll scopar resultaten till userId (owner eller shared)', async () => {
    prismaMock.recipe.findMany.mockResolvedValue([]);

    await service.findAll(42);

    expect(prismaMock.recipe.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          OR: expect.arrayContaining([
            { ownerId: 42 },
            { isPublic: true },
            { shares: { some: { userId: 42 } } },
          ]),
        }),
      }),
    );
  });

  it('findOne nekar access om recipe inte är owner/shared/public', async () => {
    prismaMock.recipe.findFirst.mockResolvedValue(null);

    await expect(service.findOne(1, 42)).rejects.toThrow(NotFoundException);
  });

  it('findOne tillåter owner att läsa eget recipe', async () => {
    prismaMock.recipe.findFirst.mockResolvedValue({
      id: 1,
      ownerId: 42,
      isPublic: false,
      name: 'Test Recipe',
      description: '',
      instructions: '',
      mealPlanDayOfWeek: null,
      imageUrl: null,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    const result = await service.findOne(1, 42);
    expect(result).toBeDefined();
  });

  it('findOne tillåter shared user att läsa shared recipe', async () => {
    prismaMock.recipe.findFirst.mockResolvedValue({
      id: 1,
      ownerId: 100,
      isPublic: false,
      shares: [{ userId: 42 }],
    });

    const result = await service.findOne(1, 42);
    expect(result).toBeDefined();
  });

  it('findOne tillåter alla att läsa public recipe', async () => {
    prismaMock.recipe.findFirst.mockResolvedValue({
      id: 1,
      ownerId: 100,
      isPublic: true,
    });

    const result = await service.findOne(1, 42);
    expect(result).toBeDefined();
  });

  it.each([
    {
      name: 'update',
      action: () => service.update(1, {} as any, 42),
    },
    {
      name: 'remove',
      action: () => service.remove(1, 42),
    },
    {
      name: 'shareWithUser',
      action: () => service.shareWithUser(1, 42, 'someuser'),
    },
    {
      name: 'unshareWithUser',
      action: () => service.unshareWithUser(1, 42, 'someuser'),
    },
    {
      name: 'setVisibility',
      action: () => service.setVisibility(1, 42, true),
    },
    {
      name: 'addIngredient',
      action: () => service.addIngredient(1, { productId: 1, quantity: 100 } as any, 42),
    },
    {
      name: 'updateImage',
      action: () => service.updateImage(1, 'http://example.com/image.jpg', 42),
    },
  ])('%s nekar icke-owner', async ({ action }) => {
    setRecipeAsOwnedByAnotherUser();

    await expect(action()).rejects.toThrow(NotFoundException);
  });
});
