import { IS_PUBLIC_KEY } from '../auth/decorators/public.decorator';
import { ProductsController } from './products.controller';
import { getRolesMetadata } from '../test-utils/security-test-helpers';

describe('Products controller security', () => {
  const productsServiceMock = {
    findByOwner: jest.fn(),
    findOne: jest.fn(),
    createPrivate: jest.fn(),
    createPending: jest.fn(),
    updateCanonicalNamePrivate: jest.fn(),
    mergePrivate: jest.fn(),
    updateCategoryMine: jest.fn(),
  };

  const aiServiceMock = {
    suggestCategory: jest.fn(),
  };

  const categoriesServiceMock = {
    findFlattened: jest.fn(),
  };

  const controller = new ProductsController(
    productsServiceMock as any,
    aiServiceMock as any,
    categoriesServiceMock as any,
  );

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

  it.each([
    ['findAll', ProductsController.prototype.findAll],
    ['findAllTags', ProductsController.prototype.findAllTags],
  ])('endpoint %s har @Public metadata', (_name, handler) => {
    const isPublic = Reflect.getMetadata(IS_PUBLIC_KEY, handler) as boolean | undefined;
    expect(isPublic).toBe(true);
  });

  it('alla admin-endpoints har @Roles("admin") metadata', () => {
    for (const [, handler] of [
      ['findDuplicates', ProductsController.prototype.findDuplicates],
      ['previewMerge', ProductsController.prototype.previewMerge],
      ['backfillCanonical', ProductsController.prototype.backfillCanonical],
      ['findPending', ProductsController.prototype.findPending],
      ['findPrivate', ProductsController.prototype.findPrivate],
      ['aiCategorizeBulk', ProductsController.prototype.aiCategorizeBulk],
      ['findDeleted', ProductsController.prototype.findDeleted],
      ['create', ProductsController.prototype.create],
      ['merge', ProductsController.prototype.merge],
      ['updateCanonicalName', ProductsController.prototype.updateCanonicalName],
      ['setTags', ProductsController.prototype.setTags],
      ['upsertNutrition', ProductsController.prototype.upsertNutrition],
      ['update', ProductsController.prototype.update],
      ['promotePrivateToGlobal', ProductsController.prototype.promotePrivateToGlobal],
      ['permanentDelete', ProductsController.prototype.permanentDelete],
      ['remove', ProductsController.prototype.remove],
      ['setStatus', ProductsController.prototype.setStatus],
      ['restore', ProductsController.prototype.restore],
      ['resetAll', ProductsController.prototype.resetAll],
      ['bulkUpdate', ProductsController.prototype.bulkUpdate],
    ]) {
      expect(getRolesMetadata(handler as Function)).toEqual(['admin']);
    }
  });

  it('findMine vidarebefordrar req.user.id till owner-scope', () => {
    productsServiceMock.findByOwner.mockResolvedValue([]);

    controller.findMine({ user: { id: 42 } } as any);

    expect(productsServiceMock.findByOwner).toHaveBeenCalledWith(42);
  });

  it('createPrivate vidarebefordrar req.user.id', () => {
    const dto = { name: 'Private Product' };
    productsServiceMock.createPrivate.mockResolvedValue({ id: 1 });

    controller.createPrivate(dto as any, { user: { id: 42 } } as any);

    expect(productsServiceMock.createPrivate).toHaveBeenCalledWith(dto, 42);
  });

  it('createPending vidarebefordrar req.user.id', () => {
    const dto = { name: 'Pending Product' };
    productsServiceMock.createPending.mockResolvedValue({ id: 1 });

    controller.createPending(dto as any, { user: { id: 42 } } as any);

    expect(productsServiceMock.createPending).toHaveBeenCalledWith(dto, 42);
  });

  it('updateCanonicalNamePrivate vidarebefordrar req.user.id', () => {
    productsServiceMock.updateCanonicalNamePrivate.mockResolvedValue({ id: 1 });

    controller.updateCanonicalNamePrivate(1, { canonicalName: 'milk' } as any, { user: { id: 42 } } as any);

    expect(productsServiceMock.updateCanonicalNamePrivate).toHaveBeenCalledWith(42, 1, 'milk');
  });

  it('mergePrivate vidarebefordrar req.user.id', () => {
    productsServiceMock.mergePrivate.mockResolvedValue({ merged: true });

    controller.mergePrivate({ sourceProductId: 10, targetProductId: 20 } as any, { user: { id: 42 } } as any);

    expect(productsServiceMock.mergePrivate).toHaveBeenCalledWith(42, 10, 20);
  });

  it('updateCategoryMine vidarebefordrar req.user.id', () => {
    productsServiceMock.updateCategoryMine.mockResolvedValue({ id: 1, categoryId: 5 });

    controller.updateCategoryMine(1, { categoryId: 5 } as any, { user: { id: 42 } } as any);

    expect(productsServiceMock.updateCategoryMine).toHaveBeenCalledWith(42, 1, 5);
  });

  it('suggestCategory använder canonicalName fallback name', async () => {
    productsServiceMock.findOne.mockResolvedValue({ id: 1, name: 'Mjolk', canonicalName: 'Mjolk 1L' });
    categoriesServiceMock.findFlattened.mockResolvedValue([{ id: 1, name: 'Mejeri' }]);
    aiServiceMock.suggestCategory.mockResolvedValue({ categoryId: 1 });

    await controller.suggestCategory(1);

    expect(aiServiceMock.suggestCategory).toHaveBeenCalledWith('Mjolk 1L', [{ id: 1, name: 'Mejeri' }]);
  });
});
