import { LeadEntity } from '../../domain/entities/lead.entity';
import { LeadScore } from '../../domain/value-objects/lead-score.vo';
import { PrismaLeadRepository } from '../repositories/prisma-lead.repository';

describe('PrismaLeadRepository', () => {
  let repository: PrismaLeadRepository;
  let mockPrisma: {
    lead: {
      findUnique: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
      update: ReturnType<typeof vi.fn>;
      delete: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
  };

  beforeEach(() => {
    mockPrisma = {
      lead: {
        findUnique: vi.fn(),
        create: vi.fn(),
        update: vi.fn(),
        delete: vi.fn(),
        findMany: vi.fn(),
        count: vi.fn(),
      },
    };
    repository = new PrismaLeadRepository(mockPrisma as any);
  });

  describe('findById', () => {
    it('returns a LeadEntity when found', async () => {
      const now = new Date();
      mockPrisma.lead.findUnique.mockResolvedValue({
        id: 'lead-1',
        agentId: 'agent-1',
        name: 'Nguyễn Văn A',
        phone: '0901234567',
        email: 'a@example.com',
        source: 'website',
        score: 80,
        notes: { interest: 'căn hộ' },
        status: 'NEW',
        createdAt: now,
        updatedAt: now,
      });

      const result = await repository.findById('lead-1');

      expect(result).toBeInstanceOf(LeadEntity);
      expect(result!.id).toBe('lead-1');
      expect(result!.agentId).toBe('agent-1');
      expect(result!.name).toBe('Nguyễn Văn A');
      expect(result!.phone).toBe('0901234567');
      expect(result!.email).toBe('a@example.com');
      expect(result!.source).toBe('website');
      expect(result!.score!.value).toBe(80);
      expect(result!.status).toBe('NEW');
    });

    it('returns a LeadEntity with null score', async () => {
      const now = new Date();
      mockPrisma.lead.findUnique.mockResolvedValue({
        id: 'lead-2',
        agentId: 'agent-1',
        name: 'Trần Thị B',
        phone: '0902345678',
        email: null,
        source: 'referral',
        score: null,
        notes: null,
        status: 'CONTACTED',
        createdAt: now,
        updatedAt: now,
      });

      const result = await repository.findById('lead-2');

      expect(result!.score).toBeNull();
      expect(result!.email).toBeNull();
    });

    it('returns null when not found', async () => {
      mockPrisma.lead.findUnique.mockResolvedValue(null);

      const result = await repository.findById('nonexistent');

      expect(result).toBeNull();
    });
  });

  describe('save', () => {
    it('creates a Prisma record from the entity', async () => {
      mockPrisma.lead.create.mockResolvedValue(undefined);
      const score = LeadScore.create(80).unwrap();
      const entity = LeadEntity.createNew(
        'lead-1',
        'agent-1',
        'Nguyễn Văn A',
        '0901234567',
        'a@example.com',
        'website',
        score,
        { interest: 'căn hộ' },
      );

      await repository.save(entity);

      expect(mockPrisma.lead.create).toHaveBeenCalledWith({
        data: {
          id: 'lead-1',
          agentId: 'agent-1',
          name: 'Nguyễn Văn A',
          phone: '0901234567',
          email: 'a@example.com',
          source: 'website',
          score: 80,
          notes: { interest: 'căn hộ' },
          status: 'NEW',
        },
      });
    });

    it('saves entity with null score and email', async () => {
      mockPrisma.lead.create.mockResolvedValue(undefined);
      const entity = LeadEntity.createNew(
        'lead-2',
        'agent-1',
        'Trần Thị B',
        '0902345678',
        null,
        'referral',
        null,
        null,
      );

      await repository.save(entity);

      const callData = mockPrisma.lead.create.mock.calls[0]![0].data;
      expect(callData.score).toBeNull();
      expect(callData.email).toBeNull();
    });
  });

  describe('update', () => {
    it('updates the entity status and score', async () => {
      mockPrisma.lead.update.mockResolvedValue(undefined);
      const entity = LeadEntity.createNew(
        'lead-1',
        'agent-1',
        'Test',
        '0900000000',
        null,
        'website',
        null,
        null,
      );

      await repository.update(entity);

      expect(mockPrisma.lead.update).toHaveBeenCalledWith({
        where: { id: 'lead-1' },
        data: {
          status: 'NEW',
          score: null,
          notes: null,
        },
      });
    });
  });

  describe('delete', () => {
    it('deletes the lead by id', async () => {
      mockPrisma.lead.delete.mockResolvedValue(undefined);

      await repository.delete('lead-1');

      expect(mockPrisma.lead.delete).toHaveBeenCalledWith({
        where: { id: 'lead-1' },
      });
    });
  });

  describe('findByAgent', () => {
    it('returns paginated results', async () => {
      const now = new Date();
      mockPrisma.lead.findMany.mockResolvedValue([
        {
          id: 'lead-1',
          agentId: 'agent-1',
          name: 'Nguyễn Văn A',
          phone: '0901234567',
          email: 'a@example.com',
          source: 'website',
          score: 80,
          notes: null,
          status: 'NEW',
          createdAt: now,
          updatedAt: now,
        },
      ]);
      mockPrisma.lead.count.mockResolvedValue(1);

      const result = await repository.findByAgent('agent-1', null, 1, 20);

      expect(result.data).toHaveLength(1);
      expect(result.total).toBe(1);
      expect(result.page).toBe(1);
      expect(result.limit).toBe(20);
      expect(result.totalPages).toBe(1);
      expect(result.data[0]!.name).toBe('Nguyễn Văn A');
    });

    it('filters by status when provided', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);
      mockPrisma.lead.count.mockResolvedValue(0);

      await repository.findByAgent('agent-1', 'NEW', 1, 20);

      expect(mockPrisma.lead.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { agentId: 'agent-1', status: 'NEW' },
        }),
      );
    });

    it('does not include status filter when null', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);
      mockPrisma.lead.count.mockResolvedValue(0);

      await repository.findByAgent('agent-1', null, 1, 20);

      expect(mockPrisma.lead.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { agentId: 'agent-1' },
        }),
      );
    });

    it('caps limit at 100', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);
      mockPrisma.lead.count.mockResolvedValue(0);

      const result = await repository.findByAgent('agent-1', null, 1, 200);

      expect(result.limit).toBe(100);
      expect(mockPrisma.lead.findMany).toHaveBeenCalledWith(
        expect.objectContaining({ take: 100 }),
      );
    });

    it('calculates correct skip for pagination', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);
      mockPrisma.lead.count.mockResolvedValue(0);

      await repository.findByAgent('agent-1', null, 3, 10);

      expect(mockPrisma.lead.findMany).toHaveBeenCalledWith(
        expect.objectContaining({ skip: 20 }),
      );
    });

    it('calculates totalPages correctly', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);
      mockPrisma.lead.count.mockResolvedValue(45);

      const result = await repository.findByAgent('agent-1', null, 1, 20);

      expect(result.totalPages).toBe(3);
    });
  });

  describe('getStatsByAgent', () => {
    it('returns stats with conversion rate and avg score', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([
        { status: 'NEW', score: 60 },
        { status: 'CONTACTED', score: 80 },
        { status: 'CONVERTED', score: 90 },
        { status: 'LOST', score: 30 },
      ]);

      const result = await repository.getStatsByAgent('agent-1');

      expect(result.totalLeads).toBe(4);
      expect(result.byStatus).toEqual({
        NEW: 1,
        CONTACTED: 1,
        CONVERTED: 1,
        LOST: 1,
      });
      expect(result.conversionRate).toBe(25);
      expect(result.avgScore).toBe(65);
    });

    it('returns zero conversion rate for empty leads', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([]);

      const result = await repository.getStatsByAgent('agent-1');

      expect(result.totalLeads).toBe(0);
      expect(result.byStatus).toEqual({});
      expect(result.conversionRate).toBe(0);
      expect(result.avgScore).toBeNull();
    });

    it('returns null avgScore when no leads have scores', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([
        { status: 'NEW', score: null },
        { status: 'CONTACTED', score: null },
      ]);

      const result = await repository.getStatsByAgent('agent-1');

      expect(result.totalLeads).toBe(2);
      expect(result.avgScore).toBeNull();
    });

    it('calculates avgScore only from scored leads', async () => {
      mockPrisma.lead.findMany.mockResolvedValue([
        { status: 'NEW', score: 60 },
        { status: 'CONTACTED', score: null },
        { status: 'QUALIFIED', score: 80 },
      ]);

      const result = await repository.getStatsByAgent('agent-1');

      expect(result.avgScore).toBe(70);
    });
  });
});
