import { Test, type TestingModule } from '@nestjs/testing';
import { BadRequestException, ConflictException, NotFoundException } from '@nestjs/common';
import { AgentCostService } from './agent-cost.service';
import { AgentCostRepository } from './repositories/agent-cost.repository';

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

  const mockRepo = {
    findPaginated: jest.fn(),
    findById: jest.fn(),
    findByName: jest.fn(),
    findActiveById: jest.fn(),
    find: jest.fn(),
    save: jest.fn(),
    update: jest.fn(),
    softDelete: jest.fn(),
    countAssigned: jest.fn(),
    listAssigned: jest.fn(),
    listUnassigned: jest.fn(),
    assignUnassigned: jest.fn(),
    unassign: jest.fn(),
  };

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

    service = module.get(AgentCostService);
  });

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

  describe('findAll', () => {
    it('returns paginated rules with the trip sources that use them', async () => {
      mockRepo.findPaginated.mockResolvedValue({
        items: [{ id: '1', name: 'Standard', percentage: '8.00', is_active: true }],
        meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
      });
      mockRepo.listAssigned.mockResolvedValue([
        { id: 'src-1', name: 'Ajay Modi Travels', agent_cost_id: '1' },
      ]);

      const result = await service.findAll();

      expect(result.items[0].percentage).toBe(8);
      expect(result.items[0].assigned_agents).toEqual([{ id: 'src-1', name: 'Ajay Modi Travels' }]);
    });
  });

  describe('findById', () => {
    it('throws NotFoundException for a different-tenant (or missing) rule', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.findById('other-tenant-cost')).rejects.toThrow(NotFoundException);
    });
  });

  describe('create', () => {
    it('creates a percentage rule', async () => {
      mockRepo.findByName.mockResolvedValue(null);
      mockRepo.save.mockResolvedValue({ id: 'new-1' });

      const result = await service.create({ name: ' Standard ', percentage: 8 });

      expect(result).toEqual({ id: 'new-1', message: 'Agent cost created successfully' });
      expect(mockRepo.save).toHaveBeenCalledWith({
        name: 'Standard',
        percentage: '8.00',
        is_active: true,
      });
    });

    it('throws ConflictException on duplicate name', async () => {
      mockRepo.findByName.mockResolvedValue({ id: 'existing' });
      await expect(service.create({ name: 'Standard', percentage: 8 })).rejects.toThrow(
        ConflictException,
      );
      expect(mockRepo.save).not.toHaveBeenCalled();
    });
  });

  describe('remove', () => {
    it('blocks delete while a trip source still uses the rule', async () => {
      mockRepo.findById.mockResolvedValue({
        id: '1',
        name: 'Standard',
        percentage: '8.00',
        is_active: true,
      });
      mockRepo.listAssigned.mockResolvedValue([]);
      mockRepo.countAssigned.mockResolvedValue(1);

      await expect(service.remove('1')).rejects.toThrow(ConflictException);
      expect(mockRepo.softDelete).not.toHaveBeenCalled();
    });

    it('throws NotFoundException for a different-tenant rule', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.remove('other-tenant-cost')).rejects.toThrow(NotFoundException);
    });
  });

  describe('assign', () => {
    const active = {
      id: '1',
      name: 'Gold',
      percentage: '0.40',
      is_active: true,
      created_at: new Date(),
      updated_at: new Date(),
    };

    it('assigns only the ids the caller sent', async () => {
      mockRepo.findById.mockResolvedValue(active);
      mockRepo.listAssigned.mockResolvedValue([]);
      mockRepo.assignUnassigned.mockResolvedValue(undefined);

      await expect(service.assign('1', { trip_source_ids: ['src-1', 'src-1'] })).resolves.toEqual({
        id: '1',
        message: 'Agents assigned successfully',
      });
      expect(mockRepo.assignUnassigned).toHaveBeenCalledWith('1', ['src-1']);
    });

    it('rejects assign on an inactive rule', async () => {
      mockRepo.findById.mockResolvedValue({ ...active, is_active: false });
      mockRepo.listAssigned.mockResolvedValue([]);

      await expect(service.assign('1', { trip_source_ids: ['src-1'] })).rejects.toThrow(
        BadRequestException,
      );
      expect(mockRepo.assignUnassigned).not.toHaveBeenCalled();
    });
  });

  describe('unassign', () => {
    it('throws when the trip source is not on this rule', async () => {
      mockRepo.findById.mockResolvedValue({
        id: '1',
        name: 'Gold',
        percentage: '0.40',
        is_active: true,
        created_at: new Date(),
        updated_at: new Date(),
      });
      mockRepo.listAssigned.mockResolvedValue([]);
      mockRepo.unassign.mockResolvedValue(false);

      await expect(service.unassign('1', 'src-2')).rejects.toThrow(NotFoundException);
    });
  });
});
