import { CreateLeadCommand } from '../../application/commands/create-lead/create-lead.command';
import { DeleteLeadCommand } from '../../application/commands/delete-lead/delete-lead.command';
import { UpdateLeadStatusCommand } from '../../application/commands/update-lead-status/update-lead-status.command';
import { GetLeadStatsQuery } from '../../application/queries/get-lead-stats/get-lead-stats.query';
import { GetLeadsByAgentQuery } from '../../application/queries/get-leads-by-agent/get-leads-by-agent.query';
import { LeadsController } from '../controllers/leads.controller';

describe('LeadsController', () => {
  let controller: LeadsController;
  let mockCommandBus: { execute: ReturnType<typeof vi.fn> };
  let mockQueryBus: { execute: ReturnType<typeof vi.fn> };

  const mockAgent = { sub: 'agent-1', phone: '0901234567', role: 'AGENT' };

  beforeEach(() => {
    mockCommandBus = { execute: vi.fn() };
    mockQueryBus = { execute: vi.fn() };
    controller = new LeadsController(mockCommandBus as any, mockQueryBus as any);
  });

  describe('POST /leads — createLead', () => {
    it('dispatches CreateLeadCommand with correct parameters', async () => {
      const dto = {
        name: 'Nguyễn Văn A',
        phone: '0909123456',
        email: 'a@example.com',
        source: 'website',
        score: 80,
        notes: 'Quan tâm căn hộ',
      };
      const expected = { id: 'lead-1', name: 'Nguyễn Văn A' };
      mockCommandBus.execute.mockResolvedValue(expected);

      const result = await controller.createLead(dto as any, mockAgent as any);

      expect(mockCommandBus.execute).toHaveBeenCalledWith(expect.any(CreateLeadCommand));
      const cmd = mockCommandBus.execute.mock.calls[0]![0] as CreateLeadCommand;
      expect(cmd.agentUserId).toBe('agent-1');
      expect(cmd.name).toBe('Nguyễn Văn A');
      expect(cmd.phone).toBe('0909123456');
      expect(cmd.email).toBe('a@example.com');
      expect(cmd.source).toBe('website');
      expect(cmd.score).toBe(80);
      expect(cmd.notes).toBe('Quan tâm căn hộ');
      expect(result).toEqual(expected);
    });

    it('passes null for optional fields', async () => {
      const dto = { name: 'Test', phone: '0909000000', source: 'referral' };
      mockCommandBus.execute.mockResolvedValue({ id: 'lead-2' });

      await controller.createLead(dto as any, mockAgent as any);

      const cmd = mockCommandBus.execute.mock.calls[0]![0] as CreateLeadCommand;
      expect(cmd.email).toBeNull();
      expect(cmd.score).toBeNull();
      expect(cmd.notes).toBeNull();
    });
  });

  describe('GET /leads — getLeads', () => {
    it('dispatches GetLeadsByAgentQuery with defaults', async () => {
      const expected = { data: [], total: 0, page: 1, limit: 20, totalPages: 0 };
      mockQueryBus.execute.mockResolvedValue(expected);

      const result = await controller.getLeads({} as any, mockAgent as any);

      expect(mockQueryBus.execute).toHaveBeenCalledWith(expect.any(GetLeadsByAgentQuery));
      const query = mockQueryBus.execute.mock.calls[0]![0] as GetLeadsByAgentQuery;
      expect(query.agentUserId).toBe('agent-1');
      expect(query.status).toBeNull();
      expect(query.page).toBe(1);
      expect(query.limit).toBe(20);
      expect(result).toEqual(expected);
    });

    it('passes custom filters', async () => {
      mockQueryBus.execute.mockResolvedValue({ data: [] });

      await controller.getLeads({ status: 'qualified', page: 2, limit: 10 } as any, mockAgent as any);

      const query = mockQueryBus.execute.mock.calls[0]![0] as GetLeadsByAgentQuery;
      expect(query.status).toBe('qualified');
      expect(query.page).toBe(2);
      expect(query.limit).toBe(10);
    });
  });

  describe('GET /leads/stats — getStats', () => {
    it('dispatches GetLeadStatsQuery', async () => {
      const expected = { total: 50, qualified: 10, converted: 5 };
      mockQueryBus.execute.mockResolvedValue(expected);

      const result = await controller.getStats(mockAgent as any);

      expect(mockQueryBus.execute).toHaveBeenCalledWith(expect.any(GetLeadStatsQuery));
      const query = mockQueryBus.execute.mock.calls[0]![0] as GetLeadStatsQuery;
      expect(query.agentUserId).toBe('agent-1');
      expect(result).toEqual(expected);
    });
  });

  describe('PATCH /leads/:id/status — updateStatus', () => {
    it('dispatches UpdateLeadStatusCommand and returns updated true', async () => {
      mockCommandBus.execute.mockResolvedValue(undefined);

      const result = await controller.updateStatus(
        'lead-1',
        { status: 'qualified' } as any,
        mockAgent as any,
      );

      expect(mockCommandBus.execute).toHaveBeenCalledWith(expect.any(UpdateLeadStatusCommand));
      const cmd = mockCommandBus.execute.mock.calls[0]![0] as UpdateLeadStatusCommand;
      expect(cmd.leadId).toBe('lead-1');
      expect(cmd.agentUserId).toBe('agent-1');
      expect(cmd.newStatus).toBe('qualified');
      expect(result).toEqual({ updated: true });
    });
  });

  describe('DELETE /leads/:id — deleteLead', () => {
    it('dispatches DeleteLeadCommand and returns deleted true', async () => {
      mockCommandBus.execute.mockResolvedValue(undefined);

      const result = await controller.deleteLead('lead-1', mockAgent as any);

      expect(mockCommandBus.execute).toHaveBeenCalledWith(expect.any(DeleteLeadCommand));
      const cmd = mockCommandBus.execute.mock.calls[0]![0] as DeleteLeadCommand;
      expect(cmd.leadId).toBe('lead-1');
      expect(cmd.agentUserId).toBe('agent-1');
      expect(result).toEqual({ deleted: true });
    });
  });
});
