/**
 * @fileoverview Unit tests for ProductsService
 * Tests cover CRUD operations, stock queries, validation, and error handling
 */

import { Repository, SelectQueryBuilder } from 'typeorm';
import { Product, ProductType, TrackingType, ValuationMethod } from '../entities/product.entity';
import { StockQuant } from '../entities/stock-quant.entity';

// Mock the AppDataSource before importing the service
jest.mock('../../../config/typeorm.js', () => ({
  AppDataSource: {
    getRepository: jest.fn(),
  },
}));

// Mock logger
jest.mock('../../../shared/utils/logger.js', () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    debug: jest.fn(),
  },
}));

// Import after mocking
import { AppDataSource } from '../../../config/typeorm.js';

describe('ProductsService', () => {
  let mockProductRepository: Partial<Repository<Product>>;
  let mockStockQuantRepository: Partial<Repository<StockQuant>>;
  let mockQueryBuilder: Partial<SelectQueryBuilder<Product>>;

  const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
  const mockProductId = '550e8400-e29b-41d4-a716-446655440010';
  const mockUomId = '550e8400-e29b-41d4-a716-446655440020';
  const mockCategoryId = '550e8400-e29b-41d4-a716-446655440030';

  const mockProduct: Partial<Product> = {
    id: mockProductId,
    tenantId: mockTenantId,
    name: 'Test Product',
    code: 'PROD-001',
    barcode: '1234567890123',
    description: 'A test product description',
    productType: ProductType.STORABLE,
    tracking: TrackingType.NONE,
    categoryId: mockCategoryId,
    uomId: mockUomId,
    purchaseUomId: mockUomId,
    costPrice: 100.00,
    listPrice: 150.00,
    valuationMethod: ValuationMethod.STANDARD,
    weight: 1.5,
    volume: 0.5,
    canBeSold: true,
    canBePurchased: true,
    active: true,
    imageUrl: null,
    createdAt: new Date('2026-01-01'),
    updatedAt: new Date('2026-01-01'),
  };

  const mockStockQuant: Partial<StockQuant> = {
    id: '550e8400-e29b-41d4-a716-446655440040',
    tenantId: mockTenantId,
    productId: mockProductId,
    locationId: '550e8400-e29b-41d4-a716-446655440060',
    quantity: 100,
    reservedQuantity: 10,
    lotId: null,
  };

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

    // Setup mock query builder
    mockQueryBuilder = {
      leftJoinAndSelect: jest.fn().mockReturnThis(),
      where: jest.fn().mockReturnThis(),
      andWhere: jest.fn().mockReturnThis(),
      orderBy: jest.fn().mockReturnThis(),
      skip: jest.fn().mockReturnThis(),
      take: jest.fn().mockReturnThis(),
      getManyAndCount: jest.fn().mockResolvedValue([[mockProduct], 1]),
      getMany: jest.fn().mockResolvedValue([mockProduct]),
    };

    // Setup mock repositories
    mockProductRepository = {
      create: jest.fn().mockReturnValue(mockProduct),
      save: jest.fn().mockResolvedValue(mockProduct),
      findOne: jest.fn().mockResolvedValue(mockProduct),
      find: jest.fn().mockResolvedValue([mockProduct]),
      update: jest.fn().mockResolvedValue({ affected: 1 }),
      softDelete: jest.fn().mockResolvedValue({ affected: 1 }),
      createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
    };

    mockStockQuantRepository = {
      find: jest.fn().mockResolvedValue([mockStockQuant]),
      findOne: jest.fn().mockResolvedValue(mockStockQuant),
      createQueryBuilder: jest.fn().mockReturnValue({
        select: jest.fn().mockReturnThis(),
        where: jest.fn().mockReturnThis(),
        andWhere: jest.fn().mockReturnThis(),
        groupBy: jest.fn().mockReturnThis(),
        getRawMany: jest.fn().mockResolvedValue([{ total: 100, reserved: 10 }]),
      }),
    };

    // Configure AppDataSource mock
    (AppDataSource.getRepository as jest.Mock).mockImplementation((entity) => {
      if (entity === Product || entity.name === 'Product') {
        return mockProductRepository;
      }
      if (entity === StockQuant || entity.name === 'StockQuant') {
        return mockStockQuantRepository;
      }
      return {};
    });
  });

  describe('Product CRUD Operations', () => {
    it('should find all products with filters', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        page: 1,
        limit: 20,
      });

      expect(mockProductRepository.createQueryBuilder).toHaveBeenCalled();
      expect(result.data).toBeDefined();
      expect(result.total).toBe(1);
    });

    it('should filter products by search term', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        search: 'Test',
        page: 1,
        limit: 20,
      });

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

    it('should filter products by category', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        categoryId: mockCategoryId,
        page: 1,
        limit: 20,
      });

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

    it('should filter products by type', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        productType: ProductType.STORABLE,
        page: 1,
        limit: 20,
      });

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

    it('should find product by ID', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findById(
        mockProductId,
        mockTenantId
      );

      expect(mockProductRepository.findOne).toHaveBeenCalled();
      expect(result).toBeDefined();
      expect(result.id).toBe(mockProductId);
    });

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

      const { productsService } = await import('../products.service.js');

      await expect(
        productsService.findById('non-existent-id', mockTenantId)
      ).rejects.toThrow();
    });

    it('should create a new product', async () => {
      const createDto = {
        name: 'New Product',
        code: 'PROD-002',
        uomId: mockUomId,
        productType: ProductType.STORABLE,
        costPrice: 50.00,
        listPrice: 75.00,
        canBeSold: true,
        canBePurchased: true,
      };

      const { productsService } = await import('../products.service.js');

      // Service signature: create(dto, tenantId, userId)
      const result = await productsService.create(
        createDto,
        mockTenantId,
        'mock-user-id'
      );

      expect(mockProductRepository.create).toHaveBeenCalled();
      expect(mockProductRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should update an existing product', async () => {
      const updateDto = {
        name: 'Updated Product Name',
        listPrice: 175.00,
      };

      const { productsService } = await import('../products.service.js');

      // Service signature: update(id, dto, tenantId, userId)
      const result = await productsService.update(
        mockProductId,
        updateDto,
        mockTenantId,
        'mock-user-id'
      );

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

    it('should soft delete a product', async () => {
      const { productsService } = await import('../products.service.js');

      // Service signature: delete(id, tenantId, userId)
      await productsService.delete(mockProductId, mockTenantId, 'mock-user-id');

      // Service uses .update() not .softDelete() directly
      expect(mockProductRepository.update).toHaveBeenCalled();
    });
  });

  describe('Stock Operations', () => {
    it('should get product stock', async () => {
      const { productsService } = await import('../products.service.js');

      // Service signature: getStock(productId, tenantId)
      const result = await productsService.getStock(
        mockProductId,
        mockTenantId
      );

      expect(mockStockQuantRepository.createQueryBuilder).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    // TODO: Method removed, update test
    // it('should get available quantity for product', async () => {
    //   const { productsService } = await import('../products.service.js');
    //
    //   const result = await productsService.getAvailableQuantity(
    //     mockTenantId,
    //     mockCompanyId,
    //     mockProductId
    //   );
    //
    //   expect(result).toBeDefined();
    // });
  });

  describe('Validation', () => {
    it('should validate unique product code', async () => {
      // Simulate existing product with same code
      mockProductRepository.findOne = jest.fn()
        .mockResolvedValueOnce(mockProduct); // Find duplicate

      const createDto = {
        name: 'Duplicate Product',
        code: 'PROD-001', // Same code as mockProduct
        uomId: mockUomId,
      };

      // Test depends on service implementation
      expect(mockProductRepository.findOne).toBeDefined();
    });

    it('should validate unique barcode', async () => {
      mockProductRepository.findOne = jest.fn()
        .mockResolvedValueOnce(mockProduct);

      const createDto = {
        name: 'Another Product',
        barcode: '1234567890123', // Same barcode as mockProduct
        uomId: mockUomId,
      };

      expect(mockProductRepository.findOne).toBeDefined();
    });
  });

  describe('Product Types', () => {
    it('should filter storable products only', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        productType: ProductType.STORABLE,
      });

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

    it('should filter consumable products only', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        productType: ProductType.CONSUMABLE,
      });

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

    it('should filter service products only', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        productType: ProductType.SERVICE,
      });

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

  describe('Sales and Purchase Flags', () => {
    it('should filter products that can be sold', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        canBeSold: true,
      });

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

    it('should filter products that can be purchased', async () => {
      const { productsService } = await import('../products.service.js');

      const result = await productsService.findAll(mockTenantId, {
        canBePurchased: true,
      });

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