import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PropertyMediaEntity } from '../../domain/entities/property-media.entity';
import { PropertyEntity } from '../../domain/entities/property.entity';
import { PrismaPropertyRepository } from '../repositories/prisma-property.repository';

describe('PrismaPropertyRepository', () => {
  let repository: PrismaPropertyRepository;
  let mockPrisma: {
    property: {
      findUnique: ReturnType<typeof vi.fn>;
    };
    propertyMedia: {
      create: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      delete: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
    };
    $executeRaw: ReturnType<typeof vi.fn>;
    $queryRaw: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockPrisma = {
      property: {
        findUnique: vi.fn(),
      },
      propertyMedia: {
        create: vi.fn().mockResolvedValue(undefined),
        findMany: vi.fn().mockResolvedValue([]),
        delete: vi.fn().mockResolvedValue(undefined),
        count: vi.fn().mockResolvedValue(0),
      },
      $executeRaw: vi.fn().mockResolvedValue(1),
      $queryRaw: vi.fn().mockResolvedValue([]),
    };
    repository = new PrismaPropertyRepository(mockPrisma as any);
  });

  describe('findById', () => {
    it('should return null when property not found', async () => {
      mockPrisma.$queryRaw.mockResolvedValue([]);

      const result = await repository.findById('non-existent');

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

    it('should return a PropertyEntity with actual lat/lng from PostGIS', async () => {
      const now = new Date();
      // ST_Y/ST_X extract actual coordinates — no more hardcoded (0,0)
      mockPrisma.$queryRaw.mockResolvedValue([
        {
          id: 'prop-1',
          propertyType: 'APARTMENT',
          title: 'Căn hộ đẹp',
          description: 'Mô tả chi tiết',
          address: '123 Nguyễn Huệ',
          ward: 'Bến Nghé',
          district: 'Quận 1',
          city: 'Hồ Chí Minh',
          latitude: 10.7769,
          longitude: 106.7009,
          areaM2: 80,
          usableAreaM2: 70,
          bedrooms: 2,
          bathrooms: 2,
          floors: null,
          floor: 10,
          totalFloors: 25,
          direction: 'EAST',
          yearBuilt: 2022,
          legalStatus: 'Sổ hồng',
          amenities: null,
          nearbyPOIs: null,
          metroDistanceM: 300,
          projectName: 'Vinhomes',
          createdAt: now,
          updatedAt: now,
        },
      ]);

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

      expect(result).toBeInstanceOf(PropertyEntity);
      expect(result!.id).toBe('prop-1');
      expect(result!.title).toBe('Căn hộ đẹp');
      expect(result!.propertyType).toBe('APARTMENT');
      // Verify actual lat/lng are returned — not hardcoded (0, 0)
      expect(result!.location.latitude).toBe(10.7769);
      expect(result!.location.longitude).toBe(106.7009);
    });

    it('should correctly map Ho Chi Minh City coordinates', async () => {
      const now = new Date();
      mockPrisma.$queryRaw.mockResolvedValue([
        {
          id: 'prop-hcm',
          propertyType: 'TOWNHOUSE',
          title: 'Nhà phố Thủ Đức',
          description: 'Nhà phố đẹp',
          address: '456 Võ Văn Ngân',
          ward: 'Linh Chiểu',
          district: 'Thủ Đức',
          city: 'Hồ Chí Minh',
          latitude: 10.8514,
          longitude: 106.7583,
          areaM2: 120,
          usableAreaM2: 100,
          bedrooms: 3,
          bathrooms: 2,
          floors: 3,
          floor: null,
          totalFloors: null,
          direction: 'SOUTH',
          yearBuilt: 2021,
          legalStatus: 'Sổ hồng',
          amenities: { parking: true },
          nearbyPOIs: [],
          metroDistanceM: 500,
          projectName: null,
          createdAt: now,
          updatedAt: now,
        },
      ]);

      const result = await repository.findById('prop-hcm');

      expect(result).toBeInstanceOf(PropertyEntity);
      expect(result!.location.latitude).toBe(10.8514);
      expect(result!.location.longitude).toBe(106.7583);
      // Verify WKT generation works with the correct coordinates
      expect(result!.location.toWKT()).toBe('POINT(106.7583 10.8514)');
    });
  });

  describe('addMedia', () => {
    it('should call prisma.propertyMedia.create with correct data', async () => {
      const media = PropertyMediaEntity.createNew(
        'media-1',
        'prop-1',
        'https://cdn.example.com/photo.jpg',
        'image',
        0,
        'Mặt tiền',
      );

      await repository.addMedia(media);

      expect(mockPrisma.propertyMedia.create).toHaveBeenCalledTimes(1);
      const createCall = mockPrisma.propertyMedia.create.mock.calls[0]![0];
      expect(createCall.data.id).toBe('media-1');
      expect(createCall.data.propertyId).toBe('prop-1');
      expect(createCall.data.url).toBe('https://cdn.example.com/photo.jpg');
      expect(createCall.data.type).toBe('image');
      expect(createCall.data.order).toBe(0);
      expect(createCall.data.caption).toBe('Mặt tiền');
    });
  });

  describe('findMediaByPropertyId', () => {
    it('should return mapped PropertyMediaEntity array', async () => {
      const now = new Date();
      mockPrisma.propertyMedia.findMany.mockResolvedValue([
        {
          id: 'media-1',
          propertyId: 'prop-1',
          url: 'https://cdn.example.com/1.jpg',
          type: 'image',
          order: 0,
          caption: null,
          aiTags: null,
          createdAt: now,
        },
        {
          id: 'media-2',
          propertyId: 'prop-1',
          url: 'https://cdn.example.com/2.jpg',
          type: 'image',
          order: 1,
          caption: 'Phòng ngủ',
          aiTags: null,
          createdAt: now,
        },
      ]);

      const result = await repository.findMediaByPropertyId('prop-1');

      expect(result).toHaveLength(2);
      expect(result[0]).toBeInstanceOf(PropertyMediaEntity);
      expect(result[0]!.id).toBe('media-1');
      expect(result[1]!.caption).toBe('Phòng ngủ');
    });
  });

  describe('deleteMedia', () => {
    it('should call prisma.propertyMedia.delete', async () => {
      await repository.deleteMedia('media-1');

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

  describe('countMediaByPropertyId', () => {
    it('should return count from prisma', async () => {
      mockPrisma.propertyMedia.count.mockResolvedValue(5);

      const result = await repository.countMediaByPropertyId('prop-1');

      expect(result).toBe(5);
      expect(mockPrisma.propertyMedia.count).toHaveBeenCalledWith({
        where: { propertyId: 'prop-1' },
      });
    });
  });
});
