import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { InventoryService } from './inventory.service';

describe('InventoryService security', () => {
  const prismaMock = {
    inventoryItem: {
      findUnique: jest.fn(),
      findMany: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    },
    inventoryConsumption: {
      findMany: jest.fn(),
      create: jest.fn(),
    },
    product: {
      findFirst: jest.fn(),
      findUnique: jest.fn(),
    },
    user: {
      findUnique: jest.fn(),
    },
    $transaction: jest.fn(),
  };

  const service = new InventoryService(prismaMock as any);

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

  it('scopar findAll till userId', async () => {
    prismaMock.inventoryItem.findMany.mockResolvedValue([]);

    await service.findAll(42, { location: 'fridge', sort: 'bestBeforeAsc' });

    expect(prismaMock.inventoryItem.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ userId: 42, location: 'fridge' }),
      }),
    );
  });

  it('sätter userId vid create', async () => {
    prismaMock.product.findFirst.mockResolvedValue({ id: 99, ownerId: 42 });
    prismaMock.inventoryItem.create.mockResolvedValue({ id: 1 });

    await service.create(42, {
      productId: 99,
      quantity: 2,
      unit: 'st',
    } as any);

    expect(prismaMock.inventoryItem.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ userId: 42, productId: 99 }),
      }),
    );
  });

  it('nekar update om inventoryItem tillhör annan användare', async () => {
    prismaMock.inventoryItem.findUnique.mockResolvedValue({
      id: 10,
      userId: 7,
      quantity: new Prisma.Decimal(1),
    });

    await expect(service.update(10, 42, {} as any)).rejects.toThrow(ForbiddenException);
  });

  it('nekar remove om inventoryItem tillhör annan användare', async () => {
    prismaMock.inventoryItem.findUnique.mockResolvedValue({
      id: 11,
      userId: 7,
      quantity: new Prisma.Decimal(1),
    });

    await expect(service.remove(11, 42)).rejects.toThrow(ForbiddenException);
  });

  it('nekar consumption-history om inventoryItem tillhör annan användare', async () => {
    prismaMock.inventoryItem.findUnique.mockResolvedValue({
      id: 12,
      userId: 7,
      quantity: new Prisma.Decimal(1),
    });

    await expect(service.findConsumptionHistory(12, 42)).rejects.toThrow(ForbiddenException);
  });

  it('nekar consume om inventoryItem tillhör annan användare', async () => {
    prismaMock.inventoryItem.findUnique.mockResolvedValue({
      id: 13,
      userId: 7,
      quantity: new Prisma.Decimal(2),
    });

    await expect(
      service.consume(13, 42, { amountUsed: 1 } as any),
    ).rejects.toThrow(ForbiddenException);
  });

  it('createAdmin kan skapa post för explicit target userId', async () => {
    prismaMock.user.findUnique.mockResolvedValue({ id: 99 });
    prismaMock.product.findUnique.mockResolvedValue({ id: 5 });
    prismaMock.inventoryItem.create.mockResolvedValue({ id: 123, userId: 99, productId: 5 });

    await service.createAdmin(
      1,
      {
        productId: 5,
        quantity: 3,
        unit: 'st',
      } as any,
      99,
    );

    expect(prismaMock.inventoryItem.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ userId: 99, productId: 5 }),
      }),
    );
  });

  it('createAdmin kastar NotFound om target user saknas', async () => {
    prismaMock.user.findUnique.mockResolvedValue(null);

    await expect(
      service.createAdmin(
        1,
        {
          productId: 5,
          quantity: 3,
          unit: 'st',
        } as any,
        404,
      ),
    ).rejects.toThrow(NotFoundException);
  });

  it('mergeAdmin blockerar merge mellan olika användare', async () => {
    prismaMock.inventoryItem.findUnique
      .mockResolvedValueOnce({
        id: 21,
        userId: 100,
        productId: 5,
        quantity: new Prisma.Decimal(1),
        unit: 'st',
      })
      .mockResolvedValueOnce({
        id: 22,
        userId: 200,
        productId: 5,
        quantity: new Prisma.Decimal(2),
        unit: 'st',
      });

    await expect(service.mergeAdmin(21, 22)).rejects.toThrow(BadRequestException);
  });

  it('mergeAdmin blockerar merge mellan olika produkter', async () => {
    prismaMock.inventoryItem.findUnique
      .mockResolvedValueOnce({
        id: 23,
        userId: 100,
        productId: 10,
        quantity: new Prisma.Decimal(1),
        unit: 'st',
      })
      .mockResolvedValueOnce({
        id: 24,
        userId: 100,
        productId: 11,
        quantity: new Prisma.Decimal(2),
        unit: 'st',
      });

    await expect(service.mergeAdmin(23, 24)).rejects.toThrow(BadRequestException);
  });

  it('previewMergeAdmin returnerar canMerge=false för olika enhet', async () => {
    prismaMock.inventoryItem.findUnique
      .mockResolvedValueOnce({
        id: 31,
        userId: 300,
        productId: 12,
        quantity: new Prisma.Decimal(1),
        unit: 'st',
      })
      .mockResolvedValueOnce({
        id: 32,
        userId: 300,
        productId: 12,
        quantity: new Prisma.Decimal(2),
        unit: 'kg',
      });

    const result = await service.previewMergeAdmin(31, 32);
    expect(result.canMerge).toBe(false);
    expect(result.reason).toBe('Cannot merge inventory items with different units');
  });

  it('andra merge-försök med redan borttagen source ger NotFound (race-liknande)', async () => {
    const tx = {
      inventoryItem: {
        update: jest.fn().mockResolvedValue({ id: 52 }),
        delete: jest.fn().mockResolvedValue({ id: 51 }),
      },
      inventoryConsumption: {
        updateMany: jest.fn().mockResolvedValue({ count: 0 }),
      },
    };

    prismaMock.$transaction.mockImplementation(async (cb: any) => cb(tx));

    prismaMock.inventoryItem.findUnique
      .mockResolvedValueOnce({
        id: 51,
        userId: 10,
        productId: 5,
        quantity: new Prisma.Decimal(1),
        unit: 'st',
      })
      .mockResolvedValueOnce({
        id: 52,
        userId: 10,
        productId: 5,
        quantity: new Prisma.Decimal(2),
        unit: 'st',
      })
      .mockResolvedValueOnce(null)
      .mockResolvedValueOnce({
        id: 52,
        userId: 10,
        productId: 5,
        quantity: new Prisma.Decimal(3),
        unit: 'st',
      });

    await expect(service.mergeAdmin(51, 52)).resolves.toBeDefined();
    await expect(service.mergeAdmin(51, 52)).rejects.toThrow(NotFoundException);
  });
});
