import { Test, type TestingModule } from '@nestjs/testing';
import { ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { ServiceTypeService } from './service-type.service';
import { ServiceTypeRepository } from './repositories/service-type.repository';
import { ServiceTypeMediaRepository } from './repositories/service-type-media.repository';
import { MediaService } from '../../common/media/media.service';

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

  const mockRepo = {
    findAllSorted: jest.fn(),
    findPaginated: jest.fn(),
    findAllActive: jest.fn(),
    findById: jest.fn(),
    findByCode: jest.fn(),
    save: jest.fn(),
    update: jest.fn(),
    softDelete: jest.fn(),
  };

  const mockMedia = {
    list: jest.fn().mockResolvedValue([]),
    prepare: jest.fn(),
    sync: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ServiceTypeService,
        { provide: ServiceTypeRepository, useValue: mockRepo },
        { provide: ServiceTypeMediaRepository, useValue: {} },
        { provide: MediaService, useValue: mockMedia },
      ],
    }).compile();

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

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

  describe('findAll', () => {
    it('should return paginated service types', async () => {
      const items = [{ id: '1', name: 'Hotels', code: 'hotels' }];
      const paginated = {
        items,
        meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
      };
      mockRepo.findPaginated.mockResolvedValue(paginated);

      const result = await service.findAll();

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

  describe('findAllActive', () => {
    it('should return only active service types', async () => {
      const items = [{ id: '1', name: 'Hotels', is_active: true }];
      mockRepo.findAllActive.mockResolvedValue(items);

      const result = await service.findAllActive();

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

  describe('findById', () => {
    it('should return the service type when found', async () => {
      const item = { id: '1', name: 'Hotels' };
      mockRepo.findById.mockResolvedValue(item);

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

      expect(result).toEqual({ ...item, media: [] });
    });

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

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

  describe('create', () => {
    const createDto = { name: 'Transport', code: 'transport' };

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

      const result = await service.create(createDto);

      expect(result).toEqual({ id: 'new-1', message: 'Service type created successfully' });
      expect(mockRepo.save).toHaveBeenCalledWith(expect.objectContaining({ is_system: false }));
    });

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

      await expect(service.create(createDto as any)).rejects.toThrow(ConflictException);
    });
  });

  describe('update', () => {
    const existingItem = { id: '1', code: 'hotels' };

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

      const result = await service.update('1', { name: 'Updated' });

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

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

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

    it('should throw ConflictException when code conflicts', async () => {
      mockRepo.findById.mockResolvedValue(existingItem);
      mockRepo.findByCode.mockResolvedValue({ id: 'other-id' });

      await expect(service.update('1', { code: 'taken' } as any)).rejects.toThrow(
        ConflictException,
      );
    });

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

      await service.update('1', { code: 'hotels' });

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

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

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

      expect(result).toEqual({ message: 'Service type deleted successfully' });
    });

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

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

    it('should throw BadRequestException when deleting a system service type', async () => {
      mockRepo.findById.mockResolvedValue({ id: '1', is_system: true });

      await expect(service.remove('1')).rejects.toThrow(BadRequestException);
    });
  });
});
