import type { IAgentRepository } from '../../domain/repositories/agent.repository';
import { RecalculateQualityScoreCommand } from '../commands/recalculate-quality-score/recalculate-quality-score.command';
import { RecalculateQualityScoreHandler } from '../commands/recalculate-quality-score/recalculate-quality-score.handler';

describe('RecalculateQualityScoreHandler', () => {
  let handler: RecalculateQualityScoreHandler;
  let mockAgentRepo: { [K in keyof IAgentRepository]: ReturnType<typeof vi.fn> };
  let mockPrisma: {
    review: { aggregate: ReturnType<typeof vi.fn> };
    lead: { count: ReturnType<typeof vi.fn> };
    listing: { count: ReturnType<typeof vi.fn> };
    agent: { findUnique: ReturnType<typeof vi.fn> };
  };

  beforeEach(() => {
    mockAgentRepo = {
      findByUserId: vi.fn(),
      findById: vi.fn(),
      updateQualityScore: vi.fn(),
      getDashboard: vi.fn(),
    };

    mockPrisma = {
      review: { aggregate: vi.fn() },
      lead: { count: vi.fn() },
      listing: { count: vi.fn() },
      agent: { findUnique: vi.fn() },
    };

    const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };

    handler = new RecalculateQualityScoreHandler(
      mockAgentRepo as any,
      mockPrisma as any,
      mockLogger as any,
    );
  });

  it('recalculates quality score successfully', async () => {
    mockAgentRepo.findById.mockResolvedValue({ id: 'agent-1', userId: 'user-1', qualityScore: 50 });
    mockPrisma.review.aggregate.mockResolvedValue({
      _avg: { rating: 4.5 },
      _count: { rating: 10 },
    });
    mockPrisma.lead.count
      .mockResolvedValueOnce(20) // totalLeads
      .mockResolvedValueOnce(5);  // convertedLeads
    mockPrisma.listing.count
      .mockResolvedValueOnce(10) // totalListings
      .mockResolvedValueOnce(7);  // activeListings
    mockPrisma.agent.findUnique.mockResolvedValue({ responseTimeAvg: 900 }); // 15 min

    const command = new RecalculateQualityScoreCommand('agent-1');

    await handler.execute(command);

    expect(mockAgentRepo.updateQualityScore).toHaveBeenCalledTimes(1);
    expect(mockAgentRepo.updateQualityScore).toHaveBeenCalledWith(
      'agent-1',
      expect.any(Number),
    );

    // Verify the score value is reasonable
    const actualScore = mockAgentRepo.updateQualityScore.mock.calls[0][1];
    expect(actualScore).toBeGreaterThan(0);
    expect(actualScore).toBeLessThanOrEqual(100);
  });

  it('skips recalculation when agent not found', async () => {
    mockAgentRepo.findById.mockResolvedValue(null);

    const command = new RecalculateQualityScoreCommand('non-existent');

    await handler.execute(command);

    expect(mockAgentRepo.updateQualityScore).not.toHaveBeenCalled();
    expect(mockPrisma.review.aggregate).not.toHaveBeenCalled();
  });

  it('updates agent record with calculated score', async () => {
    mockAgentRepo.findById.mockResolvedValue({ id: 'agent-1', userId: 'user-1', qualityScore: 0 });
    mockPrisma.review.aggregate.mockResolvedValue({
      _avg: { rating: 5 },
      _count: { rating: 20 },
    });
    mockPrisma.lead.count
      .mockResolvedValueOnce(10)
      .mockResolvedValueOnce(10); // 100% conversion
    mockPrisma.listing.count
      .mockResolvedValueOnce(5)
      .mockResolvedValueOnce(5); // 100% active
    mockPrisma.agent.findUnique.mockResolvedValue({ responseTimeAvg: 0 }); // instant response

    const command = new RecalculateQualityScoreCommand('agent-1');

    await handler.execute(command);

    expect(mockAgentRepo.updateQualityScore).toHaveBeenCalledWith('agent-1', 100);
  });

  it('handles null response time avg from agent record', async () => {
    mockAgentRepo.findById.mockResolvedValue({ id: 'agent-1', userId: 'user-1', qualityScore: 0 });
    mockPrisma.review.aggregate.mockResolvedValue({
      _avg: { rating: null },
      _count: { rating: 0 },
    });
    mockPrisma.lead.count.mockResolvedValue(0);
    mockPrisma.listing.count.mockResolvedValue(0);
    mockPrisma.agent.findUnique.mockResolvedValue({ responseTimeAvg: null });

    const command = new RecalculateQualityScoreCommand('agent-1');

    await handler.execute(command);

    // no reviews (50*0.4=20), null response (50*0.3=15), 0 conversion, 0 listing
    expect(mockAgentRepo.updateQualityScore).toHaveBeenCalledWith('agent-1', 35);
  });
});
