import type { EventBus } from '@nestjs/cqrs';
import { LeadEntity } from '../../domain/entities/lead.entity';
import type { ILeadRepository } from '../../domain/repositories/lead.repository';
import { UpdateLeadStatusCommand } from '../commands/update-lead-status/update-lead-status.command';
import { UpdateLeadStatusHandler } from '../commands/update-lead-status/update-lead-status.handler';

describe('UpdateLeadStatusHandler', () => {
  let handler: UpdateLeadStatusHandler;
  let mockLeadRepo: { [K in keyof ILeadRepository]: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };
  let mockPrisma: {
    agent: { findUnique: ReturnType<typeof vi.fn> };
  };

  beforeEach(() => {
    mockLeadRepo = {
      findById: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      findByAgent: vi.fn(),
      getStatsByAgent: vi.fn(),
    };

    mockEventBus = { publish: vi.fn() };

    mockPrisma = {
      agent: { findUnique: vi.fn() },
    };

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

    handler = new UpdateLeadStatusHandler(
      mockLeadRepo as any,
      mockEventBus as unknown as EventBus,
      mockPrisma as any,
      mockLogger as any,
    );
  });

  it('updates lead status successfully', async () => {
    mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-1' });

    const lead = new LeadEntity('lead-1', {
      agentId: 'agent-1',
      name: 'Nguyễn Văn A',
      phone: '0901234567',
      email: null,
      source: 'WEBSITE',
      score: null,
      notes: null,
      status: 'NEW',
    });
    mockLeadRepo.findById.mockResolvedValue(lead);
    mockLeadRepo.update.mockResolvedValue(undefined);

    const command = new UpdateLeadStatusCommand('lead-1', 'user-1', 'CONTACTED');

    await handler.execute(command);

    expect(mockLeadRepo.update).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('throws NotFoundException when agent not found', async () => {
    mockPrisma.agent.findUnique.mockResolvedValue(null);

    const command = new UpdateLeadStatusCommand('lead-1', 'not-an-agent', 'CONTACTED');

    await expect(handler.execute(command)).rejects.toThrow(
      "Agent with id 'not-an-agent' not found",
    );
  });

  it('throws NotFoundException when lead not found', async () => {
    mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-1' });
    mockLeadRepo.findById.mockResolvedValue(null);

    const command = new UpdateLeadStatusCommand('lead-not-exist', 'user-1', 'CONTACTED');

    await expect(handler.execute(command)).rejects.toThrow(
      "Lead with id 'lead-not-exist' not found",
    );
  });

  it('throws ForbiddenException when agent does not own the lead', async () => {
    mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-2' });

    const lead = new LeadEntity('lead-1', {
      agentId: 'agent-1',
      name: 'Nguyễn Văn A',
      phone: '0901234567',
      email: null,
      source: 'WEBSITE',
      score: null,
      notes: null,
      status: 'NEW',
    });
    mockLeadRepo.findById.mockResolvedValue(lead);

    const command = new UpdateLeadStatusCommand('lead-1', 'user-2', 'CONTACTED');

    await expect(handler.execute(command)).rejects.toThrow(
      'Bạn chỉ có thể cập nhật lead của chính mình',
    );
  });
});
