import { Test } from '@nestjs/testing';
import { NotFoundException } from '@nestjs/common';
import { DriverService } from './driver.service';
import { DriverRepository } from './repositories/driver.repository';

describe('DriverService', () => {
  let service: DriverService;
  const mockRepo = {
    find: jest.fn(),
    save: jest.fn(),
    tenantSupplierExists: jest.fn(),
    tenantDestinationExists: jest.fn(),
    findDriversBusyOn: jest.fn(),
    resolveSupplierDestination: jest.fn(),
    withSupplierLock: jest.fn((_id: string, fn: () => Promise<unknown>) => fn()),
  };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [DriverService, { provide: DriverRepository, useValue: mockRepo }],
    }).compile();
    service = module.get(DriverService);
    mockRepo.tenantSupplierExists.mockResolvedValue(true);
    mockRepo.tenantDestinationExists.mockResolvedValue(true);
    mockRepo.resolveSupplierDestination.mockResolvedValue('dest-1');
    mockRepo.findDriversBusyOn.mockResolvedValue(new Set());
    mockRepo.save.mockImplementation(async (d: Record<string, unknown>) => ({ id: 'new', ...d }));
  });

  afterEach(() => jest.clearAllMocks());

  describe('findOrCreatePlaceholder', () => {
    it('reuses the lowest placeholder free on the date instead of creating a new one', async () => {
      mockRepo.find.mockResolvedValue([
        { id: 'd2', name: 'Driver 2', destination_id: 'dest-1' },
        { id: 'd1', name: 'Driver 1', destination_id: 'dest-1' },
      ]);
      const res = await service.findOrCreatePlaceholder({ supplier_id: 's1', date: '2026-10-20' });
      expect(res).toEqual({ id: 'd1', name: 'Driver 1', created: false });
      expect(mockRepo.save).not.toHaveBeenCalled();
    });

    it('skips placeholders already busy on the date', async () => {
      mockRepo.find.mockResolvedValue([
        { id: 'd1', name: 'Driver 1', destination_id: 'dest-1' },
        { id: 'd2', name: 'Driver 2', destination_id: 'dest-1' },
      ]);
      mockRepo.findDriversBusyOn.mockResolvedValue(new Set(['d1']));
      const res = await service.findOrCreatePlaceholder({ supplier_id: 's1', date: '2026-10-20' });
      expect(res.id).toBe('d2');
    });

    it('creates the next number when every placeholder is busy, using the supplier destination', async () => {
      mockRepo.find.mockResolvedValue([
        { id: 'd1', name: 'Driver 1', destination_id: 'other' },
        { id: 'r1', name: 'Ramesh', destination_id: 'other' },
      ]);
      mockRepo.findDriversBusyOn.mockResolvedValue(new Set(['d1']));
      const res = await service.findOrCreatePlaceholder({ supplier_id: 's1', date: '2026-10-20' });
      expect(res).toEqual({ id: 'new', name: 'Driver 2', created: true });
      expect(mockRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({ supplier_id: 's1', destination_id: 'dest-1', name: 'Driver 2' }),
      );
    });

    it('runs inside the per-supplier lock', async () => {
      mockRepo.find.mockResolvedValue([]);
      await service.findOrCreatePlaceholder({ supplier_id: 's1' });
      expect(mockRepo.withSupplierLock).toHaveBeenCalledWith('s1', expect.any(Function));
    });

    it('rejects a supplier from another tenant (tenant isolation)', async () => {
      mockRepo.tenantSupplierExists.mockResolvedValue(false);
      await expect(service.findOrCreatePlaceholder({ supplier_id: 'foreign' })).rejects.toThrow(
        NotFoundException,
      );
      expect(mockRepo.save).not.toHaveBeenCalled();
    });

    it('rejects a destination from another tenant', async () => {
      mockRepo.tenantDestinationExists.mockResolvedValue(false);
      await expect(
        service.findOrCreatePlaceholder({ supplier_id: 's1', destination_id: 'foreign' }),
      ).rejects.toThrow(NotFoundException);
    });
  });
});
