import { createMockRepository, createMockQueryBuilder, createMockUom, createMockUomCategory } from '../helpers';
import { NotFoundError } from '../../shared/errors';

const mockRepository = createMockRepository();
const mockQueryBuilder = createMockQueryBuilder();
mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);

const mockCategoryRepository = createMockRepository();
const mockCategoryQb = createMockQueryBuilder();
mockCategoryRepository.createQueryBuilder.mockReturnValue(mockCategoryQb);

jest.mock('../../config/typeorm', () => ({
  AppDataSource: {
    getRepository: jest.fn((entity: any) => {
      if (entity.name === 'UomCategory') return mockCategoryRepository;
      return mockRepository;
    }),
  },
}));

import { uomService } from '../../modules/core/uom.service';

describe('UomService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
    mockCategoryRepository.createQueryBuilder.mockReturnValue(mockCategoryQb);
  });

  describe('findAllCategories', () => {
    it('should return all UoM categories', async () => {
      const mockCategories = [
        createMockUomCategory({ id: '1', name: 'Unidades' }),
        createMockUomCategory({ id: '2', name: 'Peso' }),
      ];

      mockCategoryQb.getMany.mockResolvedValue(mockCategories);

      const result = await uomService.findAllCategories();

      expect(result).toEqual(mockCategories);
    });
  });

  describe('findAll', () => {
    it('should return all UoMs', async () => {
      const mockUoms = [
        createMockUom({ id: '1', code: 'unit', name: 'Unidad' }),
        createMockUom({ id: '2', code: 'kg', name: 'Kilogramo' }),
      ];

      mockQueryBuilder.getMany.mockResolvedValue(mockUoms);

      const result = await uomService.findAll({});

      expect(result).toEqual(mockUoms);
    });

    it('should accept filter options', async () => {
      mockQueryBuilder.getMany.mockResolvedValue([]);

      const result = await uomService.findAll({ categoryId: 'cat-1', active: true });

      expect(result).toEqual([]);
    });
  });

  describe('findById', () => {
    it('should return a UoM by id', async () => {
      const mockUom = createMockUom({ id: 'uom-id-1' });
      mockRepository.findOne.mockResolvedValue(mockUom);

      const result = await uomService.findById('uom-id-1');

      expect(result).toEqual(mockUom);
    });

    it('should throw NotFoundError when UoM not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      await expect(uomService.findById('non-existent')).rejects.toThrow(NotFoundError);
    });
  });

  describe('findByCode', () => {
    it('should return a UoM by code', async () => {
      const mockUom = createMockUom({ code: 'kg' });
      mockRepository.findOne.mockResolvedValue(mockUom);

      const result = await uomService.findByCode('kg');

      expect(result).toEqual(mockUom);
    });

    it('should return null when UoM not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      const result = await uomService.findByCode('xxx');

      expect(result).toBeNull();
    });
  });

  describe('create', () => {
    it('should create a new UoM', async () => {
      const mockCategory = createMockUomCategory({ id: 'cat-1' });
      const newUom = createMockUom({ id: 'new-uom', code: 'dozen', name: 'Docena' });

      mockCategoryRepository.findOne.mockResolvedValue(mockCategory);
      mockRepository.findOne.mockResolvedValue(null);
      mockRepository.create.mockReturnValue(newUom);
      mockRepository.save.mockResolvedValue(newUom);

      const result = await uomService.create({
        categoryId: 'cat-1',
        code: 'dozen',
        name: 'Docena',
      });

      expect(result).toEqual(newUom);
    });

    it('should throw NotFoundError when category not found', async () => {
      mockCategoryRepository.findOne.mockResolvedValue(null);

      await expect(
        uomService.create({
          categoryId: 'non-existent',
          code: 'tst',
          name: 'Test',
        })
      ).rejects.toThrow(NotFoundError);
    });
  });

  describe('update', () => {
    it('should update an existing UoM', async () => {
      const existingUom = createMockUom({ id: 'uom-id' });
      const updatedUom = { ...existingUom, name: 'Updated Name' };

      mockRepository.findOne.mockResolvedValue(existingUom);
      mockRepository.save.mockResolvedValue(updatedUom);

      const result = await uomService.update('uom-id', { name: 'Updated Name' });

      expect(result.name).toBe('Updated Name');
    });

    it('should throw NotFoundError when UoM not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      await expect(uomService.update('non-existent', { name: 'Test' })).rejects.toThrow(NotFoundError);
    });
  });

  describe('convertQuantity', () => {
    it('should return same quantity when from and to are the same', async () => {
      const uom = createMockUom({ id: 'same' });

      mockRepository.findOne.mockResolvedValue(uom);

      const result = await uomService.convertQuantity(10, 'same', 'same');

      expect(result).toBe(10);
    });

    it('should perform conversion between UoMs', async () => {
      const fromUom = createMockUom({ id: 'from', code: 'kg', factor: 1, uomType: 'reference', categoryId: 'cat-1' });
      const toUom = createMockUom({ id: 'to', code: 'g', factor: 1000, uomType: 'smaller', categoryId: 'cat-1' });

      mockRepository.findOne
        .mockResolvedValueOnce(fromUom)
        .mockResolvedValueOnce(toUom);

      const result = await uomService.convertQuantity(5, 'from', 'to');

      // Result depends on implementation - just verify it returns a number
      expect(typeof result).toBe('number');
    });
  });

  describe('getReferenceUom', () => {
    it('should return reference UoM for category', async () => {
      const referenceUom = createMockUom({ uomType: 'reference' });

      mockRepository.findOne.mockResolvedValue(referenceUom);

      const result = await uomService.getReferenceUom('cat-1');

      expect(result).toEqual(referenceUom);
    });

    it('should return null when no reference UoM found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      const result = await uomService.getReferenceUom('cat-1');

      expect(result).toBeNull();
    });
  });

  describe('getConversionTable', () => {
    it('should return conversion table structure', async () => {
      const category = createMockUomCategory({ id: 'cat-1', name: 'Weight' });
      const referenceUom = createMockUom({ id: 'ref', code: 'kg', name: 'Kilogram', uomType: 'reference', factor: 1 });
      const uoms = [referenceUom];

      mockCategoryRepository.findOne.mockResolvedValue(category);
      mockRepository.findOne.mockResolvedValue(referenceUom);
      mockQueryBuilder.getMany.mockResolvedValue(uoms);

      const result = await uomService.getConversionTable('cat-1');

      expect(result).toBeDefined();
      expect(result.referenceUom).toBeDefined();
    });
  });
});
