import { Test, TestingModule } from '@nestjs/testing';
import { Repository } from 'typeorm';
import { getRepositoryToken } from '@nestjs/typeorm';
import { WarehousesService } from '../warehouses.service';
import { Warehouse, WarehouseStatus } from '../entities';
import { CreateWarehouseDto, UpdateWarehouseDto } from '../dto';

describe('WarehousesService', () => {
  let service: WarehousesService;
  let warehouseRepository: Repository<Warehouse>;

  const mockWarehouse = {
    id: 'uuid-1',
    tenantId: 'tenant-1',
    code: 'WH-001',
    name: 'Main Warehouse',
    description: 'Primary storage facility',
    address: {
      street: '123 Storage St',
      city: 'Storage City',
      state: 'SC',
      zipCode: '12345',
      country: 'US',
    },
    contact: {
      name: 'John Manager',
      email: 'john@company.com',
      phone: '+1234567890',
    },
    status: WarehouseStatus.ACTIVE,
    capacity: 10000,
    currentOccupancy: 3500,
    operatingHours: {
      monday: { open: '08:00', close: '18:00' },
      tuesday: { open: '08:00', close: '18:00' },
      wednesday: { open: '08:00', close: '18:00' },
      thursday: { open: '08:00', close: '18:00' },
      friday: { open: '08:00', close: '18:00' },
      saturday: { open: '09:00', close: '14:00' },
      sunday: { open: null, close: null },
    },
    metadata: {},
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        WarehousesService,
        {
          provide: getRepositoryToken(Warehouse),
          useValue: {
            findOne: jest.fn(),
            find: jest.fn(),
            create: jest.fn(),
            save: jest.fn(),
            remove: jest.fn(),
            createQueryBuilder: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<WarehousesService>(WarehousesService);
    warehouseRepository = module.get<Repository<Warehouse>>(getRepositoryToken(Warehouse));
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('create', () => {
    it('should create a new warehouse successfully', async () => {
      const dto: CreateWarehouseDto = {
        code: 'WH-001',
        name: 'Main Warehouse',
        description: 'Primary storage facility',
        address: {
          street: '123 Storage St',
          city: 'Storage City',
          state: 'SC',
          zipCode: '12345',
          country: 'US',
        },
        contact: {
          name: 'John Manager',
          email: 'john@company.com',
          phone: '+1234567890',
        },
        capacity: 10000,
        operatingHours: {
          monday: { open: '08:00', close: '18:00' },
          tuesday: { open: '08:00', close: '18:00' },
        },
      };

      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);
      jest.spyOn(warehouseRepository, 'create').mockReturnValue(mockWarehouse as any);
      jest.spyOn(warehouseRepository, 'save').mockResolvedValue(mockWarehouse);

      const result = await service.create(dto);

      expect(warehouseRepository.findOne).toHaveBeenCalledWith({
        where: { tenantId: 'tenant-1', code: dto.code },
      });
      expect(warehouseRepository.create).toHaveBeenCalled();
      expect(warehouseRepository.save).toHaveBeenCalled();
      expect(result).toEqual(mockWarehouse);
    });

    it('should throw error if warehouse code already exists', async () => {
      const dto: CreateWarehouseDto = {
        code: 'WH-001',
        name: 'Main Warehouse',
      };

      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);

      await expect(service.create(dto)).rejects.toThrow('Warehouse code already exists');
    });
  });

  describe('findById', () => {
    it('should find warehouse by id', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);

      const result = await service.findById('uuid-1');

      expect(warehouseRepository.findOne).toHaveBeenCalledWith({
        where: { id: 'uuid-1' },
      });
      expect(result).toEqual(mockWarehouse);
    });

    it('should return null if warehouse not found', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);

      const result = await service.findById('invalid-id');

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

  describe('findByTenant', () => {
    it('should find warehouses by tenant', async () => {
      const mockWarehouses = [mockWarehouse, { ...mockWarehouse, id: 'uuid-2' }];
      jest.spyOn(warehouseRepository, 'find').mockResolvedValue(mockWarehouses as any);

      const result = await service.findByTenant('tenant-1');

      expect(warehouseRepository.find).toHaveBeenCalledWith({
        where: { tenantId: 'tenant-1' },
        order: { code: 'ASC' },
      });
      expect(result).toEqual(mockWarehouses);
    });

    it('should filter by status', async () => {
      jest.spyOn(warehouseRepository, 'find').mockResolvedValue([mockWarehouse] as any);

      const result = await service.findByTenant('tenant-1', { status: WarehouseStatus.ACTIVE });

      expect(warehouseRepository.find).toHaveBeenCalledWith({
        where: { tenantId: 'tenant-1', status: WarehouseStatus.ACTIVE },
        order: { code: 'ASC' },
      });
      expect(result).toEqual([mockWarehouse]);
    });
  });

  describe('update', () => {
    it('should update warehouse successfully', async () => {
      const dto: UpdateWarehouseDto = {
        name: 'Updated Warehouse',
        capacity: 12000,
      };

      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
      jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
        ...mockWarehouse,
        name: 'Updated Warehouse',
        capacity: 12000,
      } as any);

      const result = await service.update('uuid-1', dto);

      expect(warehouseRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
      expect(warehouseRepository.save).toHaveBeenCalled();
      expect(result.name).toBe('Updated Warehouse');
      expect(result.capacity).toBe(12000);
    });

    it('should throw error if warehouse not found', async () => {
      const dto: UpdateWarehouseDto = { name: 'Updated' };

      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);

      await expect(service.update('invalid-id', dto)).rejects.toThrow('Warehouse not found');
    });
  });

  describe('updateOccupancy', () => {
    it('should update warehouse occupancy', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
      jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
        ...mockWarehouse,
        currentOccupancy: 4000,
      } as any);

      const result = await service.updateOccupancy('uuid-1', 4000);

      expect(warehouseRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
      expect(warehouseRepository.save).toHaveBeenCalled();
      expect(result.currentOccupancy).toBe(4000);
    });

    it('should throw error if occupancy exceeds capacity', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);

      await expect(service.updateOccupancy('uuid-1', 15000)).rejects.toThrow('Occupancy cannot exceed warehouse capacity');
    });
  });

  describe('getAvailableCapacity', () => {
    it('should calculate available capacity', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);

      const result = await service.getAvailableCapacity('uuid-1');

      expect(result).toBe(6500); // 10000 - 3500
    });
  });

  describe('delete', () => {
    it('should delete warehouse successfully', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
      jest.spyOn(warehouseRepository, 'remove').mockResolvedValue(undefined);

      await service.delete('uuid-1');

      expect(warehouseRepository.remove).toHaveBeenCalledWith(mockWarehouse);
    });

    it('should throw error if warehouse not found', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);

      await expect(service.delete('invalid-id')).rejects.toThrow('Warehouse not found');
    });

    it('should throw error if warehouse has stock', async () => {
      const warehouseWithStock = { ...mockWarehouse, currentOccupancy: 1000 };
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(warehouseWithStock as any);

      await expect(service.delete('uuid-1')).rejects.toThrow('Cannot delete warehouse with existing stock');
    });
  });

  describe('getUtilizationRate', () => {
    it('should calculate utilization rate', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);

      const result = await service.getUtilizationRate('uuid-1');

      expect(result).toBe(35); // (3500 / 10000) * 100
    });
  });

  describe('getWarehousesByCity', () => {
    it('should get warehouses by city', async () => {
      jest.spyOn(warehouseRepository, 'find').mockResolvedValue([mockWarehouse] as any);

      const result = await service.getWarehousesByCity('tenant-1', 'Storage City');

      expect(warehouseRepository.find).toHaveBeenCalledWith({
        where: {
          tenantId: 'tenant-1',
          'address.city': 'Storage City',
        },
      });
      expect(result).toEqual([mockWarehouse]);
    });
  });

  describe('updateStatus', () => {
    it('should update warehouse status', async () => {
      jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
      jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
        ...mockWarehouse,
        status: WarehouseStatus.INACTIVE,
      } as any);

      const result = await service.updateStatus('uuid-1', WarehouseStatus.INACTIVE);

      expect(result.status).toBe(WarehouseStatus.INACTIVE);
    });
  });

  describe('getWarehouseStats', () => {
    it('should get warehouse statistics', async () => {
      const mockQueryBuilder = {
        where: jest.fn().mockReturnThis(),
        select: jest.fn().mockReturnThis(),
        getRawOne: jest.fn().mockResolvedValue({ total: 5, active: 4, inactive: 1, totalCapacity: 50000, totalOccupancy: 17500 }),
      };

      jest.spyOn(warehouseRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);

      const result = await service.getWarehouseStats('tenant-1');

      expect(result.totalWarehouses).toBe(5);
      expect(result.activeWarehouses).toBe(4);
      expect(result.inactiveWarehouses).toBe(1);
      expect(result.totalCapacity).toBe(50000);
      expect(result.totalOccupancy).toBe(17500);
      expect(result.averageUtilization).toBe(35); // (17500 / 50000) * 100
    });
  });
});
