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

// Mock dependencies before importing service
const mockWarehouseRepository = createMockRepository();
const mockLocationRepository = createMockRepository();
const mockQueryBuilder = createMockQueryBuilder();

jest.mock('../../../config/typeorm.js', () => ({
  AppDataSource: {
    getRepository: jest.fn((entity: any) => {
      if (entity.name === 'Warehouse') return mockWarehouseRepository;
      if (entity.name === 'WarehouseLocation') return mockLocationRepository;
      return mockWarehouseRepository;
    }),
  },
}));

// Import after mocking
import { warehousesService } from '../warehouses.service.js';

describe('WarehousesService', () => {
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';

  beforeEach(() => {
    jest.clearAllMocks();
    mockLocationRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
  });

  describe('findAll', () => {
    it('should return warehouses with pagination', async () => {
      const mockWarehouses = [
        createMockWarehouse({ id: '1', name: 'Warehouse A' }),
        createMockWarehouse({ id: '2', name: 'Warehouse B' }),
      ];

      mockWarehouseRepository.findAndCount.mockResolvedValue([mockWarehouses, 2]);

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

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

    it('should filter by search term', async () => {
      const mockWarehouses = [createMockWarehouse({ name: 'Main Warehouse' })];
      mockWarehouseRepository.findAndCount.mockResolvedValue([mockWarehouses, 1]);

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

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

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

      await warehousesService.findAll({ tenantId, isActive: true });

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

  describe('findOne', () => {
    it('should return warehouse when found', async () => {
      const mockWarehouse = createMockWarehouse();
      mockWarehouseRepository.findOne.mockResolvedValue(mockWarehouse);

      const result = await warehousesService.findOne('warehouse-uuid-1', tenantId);

      expect(result).toEqual(mockWarehouse);
      expect(mockWarehouseRepository.findOne).toHaveBeenCalledWith({
        where: {
          id: 'warehouse-uuid-1',
          tenantId,
          deletedAt: expect.anything(),
        },
      });
    });

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

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

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

  describe('findByCode', () => {
    it('should return warehouse by code', async () => {
      const mockWarehouse = createMockWarehouse({ code: 'WH-001' });
      mockWarehouseRepository.findOne.mockResolvedValue(mockWarehouse);

      const result = await warehousesService.findByCode('WH-001', tenantId);

      expect(result).toEqual(mockWarehouse);
      expect(mockWarehouseRepository.findOne).toHaveBeenCalledWith({
        where: {
          code: 'WH-001',
          tenantId,
          deletedAt: expect.anything(),
        },
      });
    });
  });

  describe('getDefault', () => {
    it('should return default warehouse', async () => {
      const mockWarehouse = createMockWarehouse({ isDefault: true });
      mockWarehouseRepository.findOne.mockResolvedValue(mockWarehouse);

      const result = await warehousesService.getDefault(tenantId);

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

    it('should return null when no default warehouse exists', async () => {
      mockWarehouseRepository.findOne.mockResolvedValue(null);

      const result = await warehousesService.getDefault(tenantId);

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

  describe('getActive', () => {
    it('should return only active warehouses', async () => {
      const mockWarehouses = [
        createMockWarehouse({ isActive: true }),
        createMockWarehouse({ isActive: true }),
      ];
      mockWarehouseRepository.find.mockResolvedValue(mockWarehouses);

      const result = await warehousesService.getActive(tenantId);

      expect(result).toHaveLength(2);
    });
  });

  describe('create', () => {
    const createDto = {
      code: 'WH-001',
      name: 'New Warehouse',
      address: '123 Main St',
      city: 'Test City',
    };

    it('should create warehouse successfully', async () => {
      const savedWarehouse = createMockWarehouse({ ...createDto });
      mockWarehouseRepository.create.mockReturnValue(savedWarehouse);
      mockWarehouseRepository.save.mockResolvedValue(savedWarehouse);

      const result = await warehousesService.create(tenantId, createDto, userId);

      expect(result).toEqual(savedWarehouse);
      expect(mockWarehouseRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          tenantId,
          code: createDto.code,
          name: createDto.name,
        })
      );
    });

    it('should unset other defaults when creating default warehouse', async () => {
      const createDtoWithDefault = { ...createDto, isDefault: true };
      mockWarehouseRepository.update.mockResolvedValue({ affected: 1 });
      mockWarehouseRepository.create.mockReturnValue(createMockWarehouse(createDtoWithDefault));
      mockWarehouseRepository.save.mockResolvedValue(createMockWarehouse(createDtoWithDefault));

      await warehousesService.create(tenantId, createDtoWithDefault, userId);

      expect(mockWarehouseRepository.update).toHaveBeenCalledWith(
        { tenantId, isDefault: true },
        { isDefault: false }
      );
    });
  });

  describe('update', () => {
    it('should update warehouse successfully', async () => {
      const existingWarehouse = createMockWarehouse();
      mockWarehouseRepository.findOne.mockResolvedValue(existingWarehouse);
      mockWarehouseRepository.save.mockResolvedValue({ ...existingWarehouse, name: 'Updated Name' });

      const result = await warehousesService.update(
        'warehouse-uuid-1',
        tenantId,
        { name: 'Updated Name' },
        userId
      );

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

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

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

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

    it('should unset other defaults when setting as default', async () => {
      const existingWarehouse = createMockWarehouse({ isDefault: false });
      mockWarehouseRepository.findOne.mockResolvedValue(existingWarehouse);
      mockWarehouseRepository.update.mockResolvedValue({ affected: 1 });
      mockWarehouseRepository.save.mockResolvedValue({ ...existingWarehouse, isDefault: true });

      await warehousesService.update(
        'warehouse-uuid-1',
        tenantId,
        { isDefault: true },
        userId
      );

      expect(mockWarehouseRepository.update).toHaveBeenCalledWith(
        { tenantId, isDefault: true },
        { isDefault: false }
      );
    });
  });

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

      const result = await warehousesService.delete('warehouse-uuid-1', tenantId);

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

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

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

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

  describe('Location operations', () => {
    const createLocationDto = {
      warehouseId: 'warehouse-uuid-1',
      code: 'LOC-001',
      name: 'Location A',
      locationType: 'shelf' as const,
    };

    beforeEach(() => {
      mockQueryBuilder.getOne.mockResolvedValue(null);
      mockQueryBuilder.getMany.mockResolvedValue([]);
      mockQueryBuilder.getManyAndCount.mockResolvedValue([[], 0]);
    });

    it('should find all locations with tenant filtering via join', async () => {
      mockQueryBuilder.getManyAndCount.mockResolvedValue([[], 0]);

      await warehousesService.findAllLocations({ tenantId, limit: 50, offset: 0 });

      expect(mockQueryBuilder.where).toHaveBeenCalledWith(
        'warehouse.tenantId = :tenantId',
        { tenantId }
      );
    });

    it('should filter locations by warehouse', async () => {
      mockQueryBuilder.getManyAndCount.mockResolvedValue([[], 0]);

      await warehousesService.findAllLocations({
        tenantId,
        warehouseId: 'warehouse-uuid',
      });

      expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith(
        'location.warehouseId = :warehouseId',
        { warehouseId: 'warehouse-uuid' }
      );
    });

    it('should filter locations by type', async () => {
      mockQueryBuilder.getManyAndCount.mockResolvedValue([[], 0]);

      await warehousesService.findAllLocations({
        tenantId,
        locationType: 'shelf',
      });

      expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith(
        'location.locationType = :locationType',
        { locationType: 'shelf' }
      );
    });

    it('should create location', async () => {
      const savedLocation = { id: '1', ...createLocationDto };
      mockLocationRepository.create.mockReturnValue(savedLocation);
      mockLocationRepository.save.mockResolvedValue(savedLocation);

      const result = await warehousesService.createLocation(tenantId, createLocationDto, userId);

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

    it('should update location', async () => {
      const existingLocation = { id: '1', ...createLocationDto };
      mockQueryBuilder.getOne.mockResolvedValue(existingLocation);
      mockLocationRepository.save.mockResolvedValue({ ...existingLocation, name: 'Updated Location' });

      const result = await warehousesService.updateLocation(
        'location-uuid',
        tenantId,
        { name: 'Updated Location' },
        userId
      );

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

    it('should delete location', async () => {
      const existingLocation = { id: '1', ...createLocationDto };
      mockQueryBuilder.getOne.mockResolvedValue(existingLocation);
      mockLocationRepository.softDelete.mockResolvedValue({ affected: 1 });

      const result = await warehousesService.deleteLocation('location-uuid', tenantId);

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

    it('should get locations by warehouse', async () => {
      const mockLocations = [{ id: '1', code: 'LOC-001' }];
      mockQueryBuilder.getMany.mockResolvedValue(mockLocations);

      const result = await warehousesService.getLocationsByWarehouse('warehouse-uuid', tenantId);

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