import type { EventBus } from '@nestjs/cqrs';
import { AgentEntity } from '../../domain/entities/agent.entity';
import type { IAgentRepository } from '../../domain/repositories/agent.repository';
import { QualityScore } from '../../domain/value-objects/quality-score.vo';
import { RecalculateQualityScoreCommand } from '../commands/recalculate-quality-score/recalculate-quality-score.command';
import { RecalculateQualityScoreHandler } from '../commands/recalculate-quality-score/recalculate-quality-score.handler';

function makeAgent(id: string, userId: string, qualityScore: number): AgentEntity {
  return new AgentEntity(id, {
    userId,
    licenseNumber: null,
    agency: null,
    qualityScore: QualityScore.fromPersistence(qualityScore),
    totalDeals: 0,
    responseTimeAvg: null,
    bio: null,
    serviceAreas: [],
    isVerified: false,
  });
}

describe('RecalculateQualityScoreHandler', () => {
  let handler: RecalculateQualityScoreHandler;
  let mockAgentRepo: { [K in keyof IAgentRepository]: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };

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

    mockEventBus = { publish: 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,
      mockEventBus as unknown as EventBus,
      mockLogger as any,
    );
  });

  it('recalculates quality score successfully', async () => {
    mockAgentRepo.findById.mockResolvedValue(makeAgent('agent-1', 'user-1', 50));
    mockAgentRepo.getQualityScoreInputs.mockResolvedValue({
      avgRating: 4.5,
      totalReviews: 10,
      responseTimeAvg: 900,
      conversionRate: 0.25,
      activeListingRatio: 0.7,
    });

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

    await handler.execute(command);

    expect(mockAgentRepo.save).toHaveBeenCalledTimes(1);

    const savedAgent = mockAgentRepo.save.mock.calls[0][0] as AgentEntity;
    expect(savedAgent.qualityScore.value).toBeGreaterThan(0);
    expect(savedAgent.qualityScore.value).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.save).not.toHaveBeenCalled();
    expect(mockAgentRepo.getQualityScoreInputs).not.toHaveBeenCalled();
  });

  it('updates agent with calculated score', async () => {
    mockAgentRepo.findById.mockResolvedValue(makeAgent('agent-1', 'user-1', 0));
    mockAgentRepo.getQualityScoreInputs.mockResolvedValue({
      avgRating: 5,
      totalReviews: 20,
      responseTimeAvg: 0,
      conversionRate: 1,
      activeListingRatio: 1,
    });

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

    await handler.execute(command);

    const savedAgent = mockAgentRepo.save.mock.calls[0][0] as AgentEntity;
    expect(savedAgent.qualityScore.value).toBe(100);
  });

  it('publishes domain events after save', async () => {
    mockAgentRepo.findById.mockResolvedValue(makeAgent('agent-1', 'user-1', 50));
    mockAgentRepo.getQualityScoreInputs.mockResolvedValue({
      avgRating: 4.5,
      totalReviews: 10,
      responseTimeAvg: 900,
      conversionRate: 0.25,
      activeListingRatio: 0.7,
    });

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

    await handler.execute(command);

    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('handles null response time avg', async () => {
    mockAgentRepo.findById.mockResolvedValue(makeAgent('agent-1', 'user-1', 0));
    mockAgentRepo.getQualityScoreInputs.mockResolvedValue({
      avgRating: 0,
      totalReviews: 0,
      responseTimeAvg: null,
      conversionRate: 0,
      activeListingRatio: 0,
    });

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

    await handler.execute(command);

    const savedAgent = mockAgentRepo.save.mock.calls[0][0] as AgentEntity;
    // no reviews (50*0.4=20), null response (50*0.3=15), 0 conversion, 0 listing
    expect(savedAgent.qualityScore.value).toBe(35);
  });
});
