import { Test, type TestingModule } from '@nestjs/testing';
import { ConflictException } from '@nestjs/common';
import { VehicleService } from './vehicle.service';
import { VehicleRepository } from './repositories/vehicle.repository';
import { VehicleMediaRepository } from './repositories/vehicle-media.repository';
import { MediaService } from '../../common/media/media.service';
import { VehicleStatus } from '../../entities/vehicle.entity';

describe('VehicleService', () => {
  let service: VehicleService;
  let manager: { findOne: jest.Mock; find: jest.Mock; save: jest.Mock };
  let mockRepo: {
    findByVehicleNo: jest.Mock;
    save: jest.Mock;
    currentTenantId: jest.Mock;
    currentUserId: jest.Mock;
    manager: typeof manager;
  };

  beforeEach(async () => {
    manager = { findOne: jest.fn(), find: jest.fn(), save: jest.fn() };
    mockRepo = {
      findByVehicleNo: jest.fn(),
      save: jest.fn(),
      currentTenantId: jest.fn().mockReturnValue('tenant-1'),
      currentUserId: jest.fn().mockReturnValue('user-1'),
      manager,
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        VehicleService,
        { provide: VehicleRepository, useValue: mockRepo },
        { provide: VehicleMediaRepository, useValue: {} },
        {
          provide: MediaService,
          useValue: { list: jest.fn(), prepare: jest.fn(), sync: jest.fn() },
        },
      ],
    }).compile();
    service = module.get(VehicleService);
  });

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

  describe('create', () => {
    const baseDto = {
      vehicle_no: 'GJ-01-X-1234',
      type: 'SEDAN',
      destination_id: 'dest-1',
    };

    it('throws ConflictException when vehicle_no already exists', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue({ id: 'existing' });
      await expect(service.create(baseDto as never)).rejects.toThrow(ConflictException);
      expect(mockRepo.save).not.toHaveBeenCalled();
    });

    it('saves the vehicle with sensible defaults applied', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'vehicle-1', ...baseDto });
      manager.findOne.mockResolvedValue(null); // skip auto-pricing branch

      await service.create(baseDto as never);

      expect(mockRepo.save).toHaveBeenCalledWith(
        expect.objectContaining({
          vehicle_no: 'GJ-01-X-1234',
          type: 'SEDAN',
          status: VehicleStatus.AVAILABLE, // default applied
          is_active: true, // default applied
        }),
      );
    });

    it('skips auto-pricing when destination has no currency_id', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'vehicle-1' });
      manager.findOne.mockResolvedValueOnce({ id: 'dest-1', currency_id: null });

      await service.create(baseDto as never);

      expect(manager.find).not.toHaveBeenCalled();
      expect(manager.save).not.toHaveBeenCalled();
    });

    it('skips auto-pricing when type is not in the vehicle-types master', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'vehicle-1' });
      manager.findOne
        .mockResolvedValueOnce({ id: 'dest-1', currency_id: 'INR' }) // destination
        .mockResolvedValueOnce(null); // vehicle type lookup misses

      await service.create(baseDto as never);

      expect(manager.find).not.toHaveBeenCalled();
      expect(manager.save).not.toHaveBeenCalled();
    });

    it('seeds transport_pricing rows for each route lacking one', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'vehicle-1' });
      manager.findOne
        .mockResolvedValueOnce({ id: 'dest-1', currency_id: 'INR' }) // destination
        .mockResolvedValueOnce({ id: 'vt-1', name: 'SEDAN' }) // vehicle type
        .mockResolvedValueOnce(null) // exists check for route-A → miss → insert
        .mockResolvedValueOnce({ id: 'tp-1' }); // exists check for route-B → hit → skip
      manager.find.mockResolvedValueOnce([
        { id: 'route-A', category: 'AIRPORT' },
        { id: 'route-B', category: 'SIGHTSEEING' },
      ]);

      await service.create(baseDto as never);

      expect(manager.save).toHaveBeenCalledTimes(1);
      expect(manager.save).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          tenant_id: 'tenant-1',
          transport_service_id: 'route-A',
          vehicle_type_id: 'vt-1',
          currency_id: 'INR',
          created_by: 'user-1',
        }),
      );
    });

    it('swallows auto-pricing errors without rejecting the vehicle create', async () => {
      mockRepo.findByVehicleNo.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'vehicle-1' });
      manager.findOne.mockRejectedValue(new Error('DB unreachable'));

      await expect(service.create(baseDto as never)).resolves.toBeDefined();
    });
  });
});
