import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { ValuationEntity } from '../../domain/entities/valuation.entity';
import { PrismaValuationRepository } from '../repositories/prisma-valuation.repository';

const makeRawRecord = (overrides: Record<string, unknown> = {}) => ({
  id: 'val-1',
  propertyId: 'prop-1',
  estimatedPrice: BigInt(5000000000),
  confidence: 0.85,
  pricePerM2: 50,
  comparables: [],
  features: {},
  modelVersion: 'v1',
  createdAt: new Date(),
  ...overrides,
});

describe('PrismaValuationRepository', () => {
  let repo: PrismaValuationRepository;
  let mockPrisma: {
    valuation: {
      findUnique: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      findFirst: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
    };
  };

  beforeEach(() => {
    mockPrisma = {
      valuation: {
        findUnique: vi.fn(),
        findMany: vi.fn(),
        findFirst: vi.fn(),
        create: vi.fn(),
      },
    };
    repo = new PrismaValuationRepository(mockPrisma as unknown as PrismaService);
  });

  it('findById returns entity when found', async () => {
    const raw = makeRawRecord();
    mockPrisma.valuation.findUnique.mockResolvedValue(raw);

    const result = await repo.findById('val-1');

    expect(result).toBeInstanceOf(ValuationEntity);
    expect(result?.id).toBe('val-1');
    expect(result?.propertyId).toBe('prop-1');
    expect(result?.estimatedPrice).toBe(BigInt(5000000000));
    expect(mockPrisma.valuation.findUnique).toHaveBeenCalledWith({ where: { id: 'val-1' } });
  });

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

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

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

  it('findByPropertyId returns array of entities', async () => {
    const raw1 = makeRawRecord({ id: 'val-1' });
    const raw2 = makeRawRecord({ id: 'val-2', estimatedPrice: BigInt(6000000000) });
    mockPrisma.valuation.findMany.mockResolvedValue([raw1, raw2]);

    const results = await repo.findByPropertyId('prop-1');

    expect(results).toHaveLength(2);
    expect(results[0]).toBeInstanceOf(ValuationEntity);
    expect(results[1]).toBeInstanceOf(ValuationEntity);
    expect(results[0].id).toBe('val-1');
    expect(results[1].id).toBe('val-2');
    expect(mockPrisma.valuation.findMany).toHaveBeenCalledWith({
      where: { propertyId: 'prop-1' },
      orderBy: { createdAt: 'desc' },
      take: 50,
    });
  });

  it('findLatestByPropertyId returns entity when found', async () => {
    const raw = makeRawRecord();
    mockPrisma.valuation.findFirst.mockResolvedValue(raw);

    const result = await repo.findLatestByPropertyId('prop-1');

    expect(result).toBeInstanceOf(ValuationEntity);
    expect(result?.id).toBe('val-1');
    expect(mockPrisma.valuation.findFirst).toHaveBeenCalledWith({
      where: { propertyId: 'prop-1' },
      orderBy: { createdAt: 'desc' },
    });
  });

  it('findLatestByPropertyId returns null when not found', async () => {
    mockPrisma.valuation.findFirst.mockResolvedValue(null);

    const result = await repo.findLatestByPropertyId('prop-missing');

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

  it('save calls prisma.valuation.create with correct data', async () => {
    mockPrisma.valuation.create.mockResolvedValue(undefined);

    const entity = ValuationEntity.createNew(
      'val-new',
      'prop-2',
      BigInt(7000000000),
      0.90,
      70,
      [{ id: 'c1' }],
      { bedrooms: 4 },
      'v2',
    );

    await repo.save(entity);

    expect(mockPrisma.valuation.create).toHaveBeenCalledWith({
      data: {
        id: 'val-new',
        propertyId: 'prop-2',
        estimatedPrice: BigInt(7000000000),
        confidence: 0.90,
        pricePerM2: 70,
        comparables: [{ id: 'c1' }],
        features: { bedrooms: 4 },
        modelVersion: 'v2',
      },
    });
  });
});
