import { type PrismaService } from '@modules/shared';
import { PrismaAVMService } from '../services/prisma-avm.service';

describe('PrismaAVMService', () => {
  let service: PrismaAVMService;
  let mockPrisma: { $queryRaw: ReturnType<typeof vi.fn>; $queryRawUnsafe: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockPrisma = {
      $queryRaw: vi.fn(),
      $queryRawUnsafe: vi.fn(),
    };
    service = new PrismaAVMService(mockPrisma as unknown as PrismaService);
  });

  describe('estimateValue', () => {
    it('throws when neither propertyId nor coordinates provided', async () => {
      await expect(service.estimateValue({})).rejects.toThrow(
        'Either propertyId or (latitude, longitude, areaM2) must be provided',
      );
    });

    it('throws when property not found', async () => {
      mockPrisma.$queryRaw.mockResolvedValue([]);

      await expect(service.estimateValue({ propertyId: 'non-existent' })).rejects.toThrow(
        'Property not found: non-existent',
      );
    });

    it('returns zero confidence when fewer than 3 comparables', async () => {
      mockPrisma.$queryRaw.mockResolvedValue([
        { latitude: 10.762, longitude: 106.66, areaM2: 80, propertyType: 'APARTMENT', yearBuilt: 2020, floor: 5, totalFloors: 20 },
      ]);
      mockPrisma.$queryRawUnsafe.mockResolvedValue([
        { property_id: 'p1', address: '1 Test', district: 'Q1', price_vnd: 5000000000n, price_per_m2: 70000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 100, published_at: new Date() },
      ]);

      const result = await service.estimateValue({ propertyId: 'prop-1' });

      expect(result.confidence).toBe(0);
      expect(result.estimatedPrice).toBe('0');
      expect(result.comparables).toHaveLength(1);
    });

    it('calculates weighted valuation with sufficient comparables', async () => {
      mockPrisma.$queryRaw.mockResolvedValue([
        { latitude: 10.762, longitude: 106.66, areaM2: 80, propertyType: 'APARTMENT', yearBuilt: 2020, floor: 5, totalFloors: 20 },
      ]);
      mockPrisma.$queryRawUnsafe.mockResolvedValue([
        { property_id: 'p1', address: '1 Test', district: 'Q1', price_vnd: 5000000000n, price_per_m2: 70000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 100, published_at: new Date() },
        { property_id: 'p2', address: '2 Test', district: 'Q1', price_vnd: 5200000000n, price_per_m2: 72000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 300, published_at: new Date() },
        { property_id: 'p3', address: '3 Test', district: 'Q1', price_vnd: 5500000000n, price_per_m2: 75000000, area_m2: 73, property_type: 'APARTMENT', distance_meters: 500, published_at: new Date() },
      ]);

      const result = await service.estimateValue({ propertyId: 'prop-1' });

      expect(result.confidence).toBeGreaterThan(0);
      expect(Number(result.estimatedPrice)).toBeGreaterThan(0);
      expect(result.pricePerM2).toBeGreaterThan(0);
      expect(result.comparables).toHaveLength(3);
      expect(result.modelVersion).toBe('avm-v1.0');
    });

    it('uses coordinates directly when no propertyId', async () => {
      mockPrisma.$queryRawUnsafe.mockResolvedValue([
        { property_id: 'p1', address: '1 Test', district: 'Q1', price_vnd: 5000000000n, price_per_m2: 70000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 100, published_at: new Date() },
        { property_id: 'p2', address: '2 Test', district: 'Q1', price_vnd: 5200000000n, price_per_m2: 72000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 300, published_at: new Date() },
        { property_id: 'p3', address: '3 Test', district: 'Q1', price_vnd: 5500000000n, price_per_m2: 75000000, area_m2: 73, property_type: 'APARTMENT', distance_meters: 500, published_at: new Date() },
      ]);

      const result = await service.estimateValue({
        latitude: 10.762,
        longitude: 106.66,
        areaM2: 80,
        propertyType: 'APARTMENT',
      });

      expect(result.confidence).toBeGreaterThan(0);
      expect(Number(result.estimatedPrice)).toBeGreaterThan(0);
      expect(mockPrisma.$queryRaw).not.toHaveBeenCalled();
    });
  });

  describe('getComparables', () => {
    it('returns comparables for a property', async () => {
      mockPrisma.$queryRaw.mockResolvedValue([
        { latitude: 10.762, longitude: 106.66, areaM2: 80, propertyType: 'APARTMENT', yearBuilt: 2020, floor: 5, totalFloors: 20 },
      ]);
      mockPrisma.$queryRawUnsafe.mockResolvedValue([
        { property_id: 'p1', address: '1 Test', district: 'Q1', price_vnd: 5000000000n, price_per_m2: 70000000, area_m2: 72, property_type: 'APARTMENT', distance_meters: 200, published_at: new Date() },
      ]);

      const result = await service.getComparables('prop-1', 3000);

      expect(result).toHaveLength(1);
      expect(result[0].propertyId).toBe('p1');
      expect(result[0].distanceMeters).toBe(200);
    });
  });
});
