import { describe, it, expect, vi, beforeEach } from 'vitest';
import { findByIdWithProperty, searchListings, findBySellerIdQuery, findSimilarListingsQuery } from '../repositories/listing-read.queries';

describe('listing-read.queries', () => {
  let mockPrisma: {
    listing: {
      findUnique: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
    $queryRaw: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockPrisma = {
      listing: {
        findUnique: vi.fn(),
        findMany: vi.fn().mockResolvedValue([]),
        count: vi.fn().mockResolvedValue(0),
      },
      $queryRaw: vi.fn().mockResolvedValue([]),
    };
  });

  describe('findByIdWithProperty', () => {
    it('should return null when listing not found', async () => {
      mockPrisma.listing.findUnique.mockResolvedValue(null);

      const result = await findByIdWithProperty(mockPrisma as any, 'non-existent');

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

    it('should return mapped ListingDetailData when listing is found', async () => {
      const now = new Date();
      mockPrisma.listing.findUnique.mockResolvedValue({
        id: 'listing-1',
        status: 'ACTIVE',
        transactionType: 'SALE',
        priceVND: 5_000_000_000n,
        pricePerM2: 62_500_000,
        rentPriceMonthly: null,
        commissionPct: 2.0,
        viewCount: 10,
        saveCount: 2,
        inquiryCount: 1,
        publishedAt: now,
        createdAt: now,
        property: {
          id: 'prop-1',
          propertyType: 'APARTMENT',
          title: 'Căn hộ đẹp',
          description: 'Mô tả',
          address: '123 Nguyễn Huệ',
          ward: 'Bến Nghé',
          district: 'Quận 1',
          city: 'Hồ Chí Minh',
          areaM2: 80,
          bedrooms: 2,
          bathrooms: 2,
          floors: null,
          direction: 'EAST',
          yearBuilt: 2022,
          legalStatus: 'Sổ hồng',
          amenities: null,
          projectName: 'Vinhomes',
          media: [
            { id: 'm-1', url: 'https://cdn.example.com/1.jpg', type: 'image', order: 0, caption: null },
          ],
        },
        seller: { id: 'seller-1', fullName: 'Nguyễn Văn A', phone: '0901234567' },
        agent: { id: 'agent-1', userId: 'user-a', agency: 'Đất Xanh' },
      });
      mockPrisma.$queryRaw.mockResolvedValue([{ latitude: 10.7769, longitude: 106.7009 }]);

      const result = await findByIdWithProperty(mockPrisma as any, 'listing-1');

      expect(result).not.toBeNull();
      expect(result!.id).toBe('listing-1');
      expect(result!.status).toBe('ACTIVE');
      expect(result!.priceVND).toBe('5000000000');
      expect(result!.property.title).toBe('Căn hộ đẹp');
      expect(result!.property.media).toHaveLength(1);
      expect(result!.seller.fullName).toBe('Nguyễn Văn A');
      expect(result!.agent!.agency).toBe('Đất Xanh');
      expect(result!.publishedAt).toBe(now.toISOString());
      expect(result!.property.latitude).toBe(10.7769);
      expect(result!.property.longitude).toBe(106.7009);
    });
  });

  describe('searchListings', () => {
    it('should return paginated results with empty data', async () => {
      mockPrisma.listing.findMany.mockResolvedValue([]);
      mockPrisma.listing.count.mockResolvedValue(0);

      const result = await searchListings(mockPrisma as any, { page: 1, limit: 20 });

      expect(result.data).toEqual([]);
      expect(result.total).toBe(0);
      expect(result.page).toBe(1);
      expect(result.limit).toBe(20);
      expect(result.totalPages).toBe(0);
    });

    it('should map listing data correctly', async () => {
      const now = new Date();
      mockPrisma.listing.findMany.mockResolvedValue([
        {
          id: 'listing-1',
          status: 'ACTIVE',
          transactionType: 'SALE',
          priceVND: 5_000_000_000n,
          pricePerM2: 62_500_000,
          viewCount: 5,
          publishedAt: now,
          property: {
            id: 'prop-1',
            propertyType: 'APARTMENT',
            title: 'Căn hộ đẹp',
            address: '123 Nguyễn Huệ',
            district: 'Quận 1',
            city: 'Hồ Chí Minh',
            areaM2: 80,
            bedrooms: 2,
            bathrooms: 2,
            media: [{ url: 'https://cdn.example.com/thumb.jpg' }],
          },
          seller: { id: 'seller-1', fullName: 'Nguyễn Văn A' },
        },
      ]);
      mockPrisma.listing.count.mockResolvedValue(1);
      mockPrisma.$queryRaw.mockResolvedValue([{ id: 'prop-1', latitude: 10.7769, longitude: 106.7009 }]);

      const result = await searchListings(mockPrisma as any, { status: 'ACTIVE', page: 1, limit: 20 });

      expect(result.data).toHaveLength(1);
      expect(result.data[0]!.id).toBe('listing-1');
      expect(result.data[0]!.priceVND).toBe('5000000000');
      expect(result.data[0]!.property.thumbnail).toBe('https://cdn.example.com/thumb.jpg');
      expect(result.data[0]!.property.latitude).toBe(10.7769);
      expect(result.data[0]!.property.longitude).toBe(106.7009);
      expect(result.total).toBe(1);
      expect(result.totalPages).toBe(1);
    });

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

      const result = await searchListings(mockPrisma as any, { limit: 500 });

      expect(result.limit).toBe(100);
    });

    it('should default page to 1 and limit to 20', async () => {
      mockPrisma.listing.findMany.mockResolvedValue([]);
      mockPrisma.listing.count.mockResolvedValue(0);

      const result = await searchListings(mockPrisma as any, {});

      expect(result.page).toBe(1);
      expect(result.limit).toBe(20);
    });
  });

  describe('findBySellerIdQuery', () => {
    it('should return paginated seller listings', async () => {
      mockPrisma.listing.findMany.mockResolvedValue([]);
      mockPrisma.listing.count.mockResolvedValue(0);

      const result = await findBySellerIdQuery(mockPrisma as any, 'seller-1', 1, 10);

      expect(result.data).toEqual([]);
      expect(result.total).toBe(0);
      expect(result.page).toBe(1);
      expect(result.limit).toBe(10);
    });

    it('should map seller listing data correctly', async () => {
      mockPrisma.listing.findMany.mockResolvedValue([
        {
          id: 'listing-1',
          status: 'ACTIVE',
          transactionType: 'SALE',
          priceVND: 3_000_000_000n,
          property: {
            id: 'prop-1',
            title: 'Nhà phố đẹp',
            district: 'Quận 3',
            city: 'Hồ Chí Minh',
            areaM2: 120,
            media: [{ url: 'https://cdn.example.com/thumb.jpg' }],
          },
        },
      ]);
      mockPrisma.listing.count.mockResolvedValue(1);

      const result = await findBySellerIdQuery(mockPrisma as any, 'seller-1', 1, 10);

      expect(result.data).toHaveLength(1);
      expect(result.data[0]!.priceVND).toBe('3000000000');
      expect(result.data[0]!.property.thumbnail).toBe('https://cdn.example.com/thumb.jpg');
    });
  });
});


import { findSimilarListingsQuery } from '../repositories/listing-read.queries';

describe('findSimilarListingsQuery', () => {
  let mockPrisma: {
    listing: {
      findUnique: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
    };
  };

  beforeEach(() => {
    mockPrisma = {
      listing: {
        findUnique: vi.fn(),
        findMany: vi.fn(),
      },
    };
  });

  it('returns empty array when source listing is not found', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue(null);
    const result = await findSimilarListingsQuery(mockPrisma as any, 'missing-id', 5);
    expect(result).toEqual([]);
    expect(mockPrisma.listing.findMany).not.toHaveBeenCalled();
  });

  it('returns mapped ListingSimilarItem array sorted by price delta', async () => {
    const basePrice = BigInt(5_000_000_000);
    mockPrisma.listing.findUnique.mockResolvedValue({
      priceVND: basePrice,
      property: { propertyType: 'APARTMENT', district: 'Quận 1', areaM2: 70 },
    });

    const now = new Date();
    mockPrisma.listing.findMany.mockResolvedValue([
      {
        id: 'listing-far',
        priceVND: BigInt(5_450_000_000),
        publishedAt: now,
        property: { title: 'Far', areaM2: 72, district: 'Quận 1', media: [] },
      },
      {
        id: 'listing-close',
        priceVND: BigInt(4_900_000_000),
        publishedAt: now,
        property: { title: 'Close', areaM2: 68, district: 'Quận 1', media: [{ url: 'https://cdn/img.jpg' }] },
      },
    ]);

    const result = await findSimilarListingsQuery(mockPrisma as any, 'source-id', 5);

    expect(result).toHaveLength(2);
    expect(result[0].id).toBe('listing-close');
    expect(result[0].thumbnailUrl).toBe('https://cdn/img.jpg');
    expect(result[1].id).toBe('listing-far');
    expect(result[1].thumbnailUrl).toBeNull();
  });

  it('limits result to requested count', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({
      priceVND: BigInt(3_000_000_000),
      property: { propertyType: 'HOUSE', district: 'Quận 3', areaM2: 50 },
    });

    const candidates = Array.from({ length: 10 }, (_, i) => ({
      id: `listing-${i}`,
      priceVND: BigInt(3_000_000_000 + i * 1_000_000),
      publishedAt: null,
      property: { title: `Title ${i}`, areaM2: 50, district: 'Quận 3', media: [] },
    }));
    mockPrisma.listing.findMany.mockResolvedValue(candidates);

    const result = await findSimilarListingsQuery(mockPrisma as any, 'source-id', 3);

    expect(result).toHaveLength(3);
  });
});
