import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { createMockRepository, createMockProduct } from '../../../__tests__/helpers.js';

// Mock dependencies before importing service
const mockProductRepository = createMockRepository();
const mockCategoryRepository = createMockRepository();

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

// Import after mocking
import { productsService } from '../products.service.js';

describe('ProductsService', () => {
  const tenantId = 'test-tenant-uuid';

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

  describe('findAll', () => {
    it('should return products with pagination', async () => {
      const mockProducts = [
        createMockProduct({ id: '1', name: 'Product A' }),
        createMockProduct({ id: '2', name: 'Product B' }),
      ];

      mockProductRepository.findAndCount.mockResolvedValue([mockProducts, 2]);

      const result = await productsService.findAll({ tenantId, limit: 50, offset: 0 });

      expect(result.data).toHaveLength(2);
      expect(result.total).toBe(2);
    });

    it('should filter by search term', async () => {
      const mockProducts = [createMockProduct({ name: 'Test Product' })];
      mockProductRepository.findAndCount.mockResolvedValue([mockProducts, 1]);

      const result = await productsService.findAll({ tenantId, search: 'Test' });

      expect(result.data).toHaveLength(1);
      expect(mockProductRepository.findAndCount).toHaveBeenCalled();
    });

    it('should filter by category', async () => {
      mockProductRepository.findAndCount.mockResolvedValue([[], 0]);

      await productsService.findAll({ tenantId, categoryId: 'cat-uuid' });

      expect(mockProductRepository.findAndCount).toHaveBeenCalled();
    });

    it('should filter by product type', async () => {
      mockProductRepository.findAndCount.mockResolvedValue([[], 0]);

      await productsService.findAll({ tenantId, productType: 'service' });

      expect(mockProductRepository.findAndCount).toHaveBeenCalled();
    });

    it('should filter by sellable status', async () => {
      mockProductRepository.findAndCount.mockResolvedValue([[], 0]);

      await productsService.findAll({ tenantId, isSellable: true });

      expect(mockProductRepository.findAndCount).toHaveBeenCalled();
    });

    it('should filter by purchasable status', async () => {
      mockProductRepository.findAndCount.mockResolvedValue([[], 0]);

      await productsService.findAll({ tenantId, isPurchasable: true });

      expect(mockProductRepository.findAndCount).toHaveBeenCalled();
    });
  });

  describe('findOne', () => {
    it('should return product when found', async () => {
      const mockProduct = createMockProduct();
      mockProductRepository.findOne.mockResolvedValue(mockProduct);

      const result = await productsService.findOne('product-uuid-1', tenantId);

      expect(result).toEqual(mockProduct);
      expect(mockProductRepository.findOne).toHaveBeenCalledWith({
        where: {
          id: 'product-uuid-1',
          tenantId,
          deletedAt: expect.anything(),
        },
        relations: ['category'],
      });
    });

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

      const result = await productsService.findOne('nonexistent-id', tenantId);

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

  describe('findBySku', () => {
    it('should return product by SKU', async () => {
      const mockProduct = createMockProduct({ sku: 'PROD-001' });
      mockProductRepository.findOne.mockResolvedValue(mockProduct);

      const result = await productsService.findBySku('PROD-001', tenantId);

      expect(result).toEqual(mockProduct);
      expect(mockProductRepository.findOne).toHaveBeenCalledWith({
        where: {
          sku: 'PROD-001',
          tenantId,
          deletedAt: expect.anything(),
        },
        relations: ['category'],
      });
    });
  });

  describe('findByBarcode', () => {
    it('should return product by barcode', async () => {
      const mockProduct = createMockProduct({ barcode: '1234567890123' });
      mockProductRepository.findOne.mockResolvedValue(mockProduct);

      const result = await productsService.findByBarcode('1234567890123', tenantId);

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

  describe('create', () => {
    const createDto = {
      sku: 'PROD-001',
      name: 'New Product',
      salePrice: 100,
      costPrice: 50,
    };

    it('should create product successfully', async () => {
      const savedProduct = createMockProduct({ ...createDto });
      mockProductRepository.create.mockReturnValue(savedProduct);
      mockProductRepository.save.mockResolvedValue(savedProduct);

      const result = await productsService.create(tenantId, createDto, 'user-uuid');

      expect(result).toEqual(savedProduct);
      expect(mockProductRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          tenantId,
          sku: createDto.sku,
          name: createDto.name,
        })
      );
    });

    it('should set createdBy field', async () => {
      const savedProduct = createMockProduct();
      mockProductRepository.create.mockReturnValue(savedProduct);
      mockProductRepository.save.mockResolvedValue(savedProduct);

      await productsService.create(tenantId, createDto, 'user-uuid');

      expect(mockProductRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          createdBy: 'user-uuid',
        })
      );
    });
  });

  describe('update', () => {
    it('should update product successfully', async () => {
      const existingProduct = createMockProduct();
      const updatedProduct = { ...existingProduct, name: 'Updated Name' };
      mockProductRepository.findOne.mockResolvedValue(existingProduct);
      mockProductRepository.save.mockResolvedValue(updatedProduct);

      const result = await productsService.update(
        'product-uuid-1',
        tenantId,
        { name: 'Updated Name' },
        'user-uuid'
      );

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

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

      const result = await productsService.update(
        'nonexistent-id',
        tenantId,
        { name: 'Test' },
        'user-uuid'
      );

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

    it('should update prices', async () => {
      const existingProduct = createMockProduct({ salePrice: 100 });
      mockProductRepository.findOne.mockResolvedValue(existingProduct);
      mockProductRepository.save.mockResolvedValue({ ...existingProduct, salePrice: 150 });

      await productsService.update(
        'product-uuid-1',
        tenantId,
        { salePrice: 150 },
        'user-uuid'
      );

      expect(mockProductRepository.save).toHaveBeenCalled();
    });
  });

  describe('delete', () => {
    it('should soft delete product', async () => {
      mockProductRepository.softDelete.mockResolvedValue({ affected: 1 });

      const result = await productsService.delete('product-uuid-1', tenantId);

      expect(result).toBe(true);
      expect(mockProductRepository.softDelete).toHaveBeenCalledWith({
        id: 'product-uuid-1',
        tenantId,
      });
    });

    it('should return false when product not found', async () => {
      mockProductRepository.softDelete.mockResolvedValue({ affected: 0 });

      const result = await productsService.delete('nonexistent-id', tenantId);

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

  describe('getSellableProducts', () => {
    it('should return only sellable products', async () => {
      const mockProducts = [createMockProduct({ isSellable: true })];
      mockProductRepository.findAndCount.mockResolvedValue([mockProducts, 1]);

      const result = await productsService.getSellableProducts(tenantId);

      expect(result.data).toHaveLength(1);
    });
  });

  describe('getPurchasableProducts', () => {
    it('should return only purchasable products', async () => {
      const mockProducts = [createMockProduct({ isPurchasable: true })];
      mockProductRepository.findAndCount.mockResolvedValue([mockProducts, 1]);

      const result = await productsService.getPurchasableProducts(tenantId);

      expect(result.data).toHaveLength(1);
    });
  });

  describe('Category operations', () => {
    it('should find all categories', async () => {
      const mockCategories = [{ id: '1', name: 'Category A' }];
      mockCategoryRepository.findAndCount.mockResolvedValue([mockCategories, 1]);

      const result = await productsService.findAllCategories({ tenantId });

      expect(result.data).toHaveLength(1);
    });

    it('should create category', async () => {
      const categoryDto = { code: 'CAT-001', name: 'New Category' };
      const savedCategory = { id: '1', ...categoryDto };
      mockCategoryRepository.create.mockReturnValue(savedCategory);
      mockCategoryRepository.save.mockResolvedValue(savedCategory);

      const result = await productsService.createCategory(tenantId, categoryDto);

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

    it('should delete category', async () => {
      mockCategoryRepository.softDelete.mockResolvedValue({ affected: 1 });

      const result = await productsService.deleteCategory('cat-uuid', tenantId);

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