import { Test, TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException } from '@nestjs/common';
import { TransportServiceService } from './transport-service.service';
import { TransportServiceRepository } from './repositories/transport-service.repository';

describe('TransportServiceService', () => {
  let service: TransportServiceService;

  const mockRepo = {
    findAllWithDestination: jest.fn(),
    findByIdWithDestination: jest.fn(),
    findByShortCode: jest.fn(),
    findById: jest.fn(),
    save: jest.fn(),
    update: jest.fn(),
    softDelete: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TransportServiceService,
        { provide: TransportServiceRepository, useValue: mockRepo },
      ],
    }).compile();

    service = module.get<TransportServiceService>(TransportServiceService);
  });

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

  describe('findAll', () => {
    it('should return all transport services with destinations', async () => {
      const items = [{ id: '1', short_code: 'TS-001' }];
      mockRepo.findAllWithDestination.mockResolvedValue(items);

      const result = await service.findAll();

      expect(result).toEqual(items);
      expect(mockRepo.findAllWithDestination).toHaveBeenCalledTimes(1);
    });

    it('should return empty array when no transport services exist', async () => {
      mockRepo.findAllWithDestination.mockResolvedValue([]);
      const result = await service.findAll();
      expect(result).toEqual([]);
    });
  });

  describe('findById', () => {
    it('should return the transport service when found', async () => {
      const item = { id: '1', short_code: 'TS-001' };
      mockRepo.findByIdWithDestination.mockResolvedValue(item);

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

      expect(result).toEqual(item);
      expect(mockRepo.findByIdWithDestination).toHaveBeenCalledWith('1');
    });

    it('should throw NotFoundException when transport service not found', async () => {
      mockRepo.findByIdWithDestination.mockResolvedValue(null);

      await expect(service.findById('999')).rejects.toThrow(NotFoundException);
    });
  });

  describe('create', () => {
    const createDto = {
      short_code: 'TS-001',
      destination_id: 'dest-1',
      category: 'transfer',
      from_city: 'City A',
      to_city: 'City B',
    };

    it('should create and return success response', async () => {
      mockRepo.findByShortCode.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'new-1' });

      const result = await service.create(createDto as any);

      expect(result).toEqual({ id: 'new-1', message: 'Transport service created successfully' });
      expect(mockRepo.save).toHaveBeenCalledTimes(1);
    });

    it('should throw ConflictException when short_code already exists', async () => {
      mockRepo.findByShortCode.mockResolvedValue({ id: 'existing' });

      await expect(service.create(createDto as any)).rejects.toThrow(ConflictException);
      expect(mockRepo.save).not.toHaveBeenCalled();
    });
  });

  describe('update', () => {
    const existingItem = { id: '1', short_code: 'TS-001' };

    it('should update and return success response', async () => {
      mockRepo.findById.mockResolvedValue(existingItem);
      mockRepo.update.mockResolvedValue(undefined);

      const result = await service.update('1', { from_city: 'New City' } as any);

      expect(result).toEqual({ id: '1', message: 'Transport service updated successfully' });
    });

    it('should throw NotFoundException when transport service not found', async () => {
      mockRepo.findById.mockResolvedValue(null);

      await expect(service.update('999', {} as any)).rejects.toThrow(NotFoundException);
    });

    it('should throw ConflictException when short_code conflicts with another service', async () => {
      mockRepo.findById.mockResolvedValue(existingItem);
      mockRepo.findByShortCode.mockResolvedValue({ id: 'other-id' });

      await expect(
        service.update('1', { short_code: 'TS-002' } as any),
      ).rejects.toThrow(ConflictException);
    });

    it('should skip uniqueness check when short_code unchanged', async () => {
      mockRepo.findById.mockResolvedValue(existingItem);
      mockRepo.update.mockResolvedValue(undefined);

      await service.update('1', { short_code: 'TS-001' } as any);

      expect(mockRepo.findByShortCode).not.toHaveBeenCalled();
    });
  });

  describe('remove', () => {
    it('should soft delete and return success message', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1' });
      mockRepo.softDelete.mockResolvedValue(undefined);

      const result = await service.remove('1');

      expect(result).toEqual({ message: 'Transport service deleted successfully' });
      expect(mockRepo.softDelete).toHaveBeenCalledWith('1');
    });

    it('should throw NotFoundException when transport service not found', async () => {
      mockRepo.findById.mockResolvedValue(null);

      await expect(service.remove('999')).rejects.toThrow(NotFoundException);
    });
  });
});
