import { Test, TestingModule } from '@nestjs/testing';
import { ProductsService } from './products.service';
import { PrismaService } from '../../../prisma/prisma.service';
import { SyncQueueService } from './sync-queue.service';

describe('ProductsService', () => {
  let service: ProductsService;
  let prisma: PrismaService;
  let syncQueueService: SyncQueueService;

  const mockPrisma = {
    product: {
      findMany: jest.fn(),
      findUnique: jest.fn(),
      findFirst: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    },
    category: {
      findMany: jest.fn(),
      findUnique: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
      updateMany: jest.fn(),
    },
    orderItem: {
      findFirst: jest.fn(),
    },
  };

  const mockSyncQueue = {
    pushProductChange: jest.fn().mockResolvedValue(true),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ProductsService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: SyncQueueService, useValue: mockSyncQueue },
      ],
    }).compile();

    service = module.get<ProductsService>(ProductsService);
    prisma = module.get<PrismaService>(PrismaService);
    syncQueueService = module.get<SyncQueueService>(SyncQueueService);
    jest.clearAllMocks();
  });

  describe('listProducts', () => {
    it('should query products by businessId and status ACTIVE', async () => {
      mockPrisma.product.findMany.mockResolvedValueOnce([
        { id: 'p1', name: 'Product 1', price: 10, status: 'ACTIVE' },
      ]);

      const result = await service.listProducts('biz-1', 'Product');
      expect(result).toHaveLength(1);
      expect(mockPrisma.product.findMany).toHaveBeenCalled();
    });

    it('should match SC301 when product SKU in DB is "SC 301" (case & space insensitive)', async () => {
      mockPrisma.product.findMany.mockResolvedValueOnce([
        { id: 'p1', name: 'White Maize Seed', sku: 'SC 301', price: 25, status: 'ACTIVE' },
      ]);

      const result = await service.listProducts('biz-1', 'SC301');
      expect(result).toHaveLength(1);
      expect(result[0].sku).toBe('SC 301');
    });

    it('should match "SC 301" when product SKU in DB is "SC301"', async () => {
      mockPrisma.product.findMany.mockResolvedValueOnce([
        { id: 'p1', name: 'White Maize Seed', sku: 'SC301', price: 25, status: 'ACTIVE' },
      ]);

      const result = await service.listProducts('biz-1', 'SC 301');
      expect(result).toHaveLength(1);
      expect(result[0].sku).toBe('SC301');
    });

    it('should match lowercase "sc301" when product SKU is "SC-301"', async () => {
      mockPrisma.product.findMany.mockResolvedValueOnce([
        { id: 'p1', name: 'Hybrid Maize Seed', sku: 'SC-301', price: 30, status: 'ACTIVE' },
      ]);

      const result = await service.listProducts('biz-1', 'sc301');
      expect(result).toHaveLength(1);
      expect(result[0].sku).toBe('SC-301');
    });

    it('should match when query is in product name with different spacing', async () => {
      mockPrisma.product.findMany.mockResolvedValueOnce([
        { id: 'p1', name: 'Seed Co SC 301 Maize Seed 50kg', sku: 'SED-01', price: 50, status: 'ACTIVE' },
      ]);

      const result = await service.listProducts('biz-1', 'sc301');
      expect(result).toHaveLength(1);
      expect(result[0].name).toContain('SC 301');
    });
  });

  describe('createProduct', () => {
    it('should create product and trigger sync queue push', async () => {
      mockPrisma.product.create.mockResolvedValueOnce({
        id: 'p-new',
        businessId: 'biz-1',
        name: 'Sneakers',
        price: 49.99,
        currency: 'USD',
        status: 'ACTIVE',
      });

      const result = await service.createProduct('biz-1', {
        name: 'Sneakers',
        price: '49.99',
        currency: 'USD',
        stockQuantity: '10',
      });

      expect(result.id).toBe('p-new');
      expect(mockPrisma.product.create).toHaveBeenCalled();
      expect(mockSyncQueue.pushProductChange).toHaveBeenCalled();
    });
  });

  describe('updateProduct', () => {
    it('should update product details correctly', async () => {
      mockPrisma.product.update.mockResolvedValueOnce({
        id: 'p-1',
        businessId: 'biz-1',
        name: 'Sneakers Updated',
        price: 59.99,
      });

      const result = await service.updateProduct('biz-1', 'p-1', {
        name: 'Sneakers Updated',
        price: '59.99',
      });

      expect(result.name).toBe('Sneakers Updated');
      expect(mockPrisma.product.update).toHaveBeenCalled();
    });
  });

  describe('deleteProduct', () => {
    it('should soft delete product if it has existing order items', async () => {
      mockPrisma.product.findFirst.mockResolvedValueOnce({
        id: 'p-1',
        businessId: 'biz-1',
        sourceSystem: 'manual',
      });
      mockPrisma.orderItem.findFirst.mockResolvedValueOnce({ id: 'item-1' });
      mockPrisma.product.update.mockResolvedValueOnce({ id: 'p-1', status: 'INACTIVE' });

      const result = await service.deleteProduct('biz-1', 'p-1');
      expect(result.status).toBe('INACTIVE');
      expect(mockPrisma.product.update).toHaveBeenCalled();
      expect(mockPrisma.product.delete).not.toHaveBeenCalled();
    });

    it('should hard delete product if no order items exist', async () => {
      mockPrisma.product.findFirst.mockResolvedValueOnce({
        id: 'p-2',
        businessId: 'biz-1',
        sourceSystem: 'manual',
      });
      mockPrisma.orderItem.findFirst.mockResolvedValueOnce(null);
      mockPrisma.product.delete.mockResolvedValueOnce({ id: 'p-2' });

      const result = await service.deleteProduct('biz-1', 'p-2');
      expect(result.id).toBe('p-2');
      expect(mockPrisma.product.delete).toHaveBeenCalled();
    });
  });
});
