import { ListingIndexerService } from '../services/listing-indexer.service';

const mockListing = {
  id: 'listing-1',
  status: 'ACTIVE',
  transactionType: 'SALE',
  priceVND: BigInt(5000000000),
  pricePerM2: 50,
  agentId: 'agent-1',
  sellerId: 'seller-1',
  publishedAt: new Date(),
  viewCount: 10,
  saveCount: 5,
  property: {
    id: 'prop-1',
    title: 'Test',
    description: 'Desc',
    propertyType: 'APARTMENT',
    areaM2: 80,
    bedrooms: 2,
    bathrooms: 1,
    floors: 1,
    direction: 'EAST',
    address: '123 Street',
    ward: 'Ward 1',
    district: 'District 1',
    city: 'HCMC',
    projectName: null,
    legalStatus: null,
    amenities: ['parking'],
  },
};

describe('ListingIndexerService', () => {
  let service: ListingIndexerService;
  let mockPrisma: {
    listing: { findUnique: ReturnType<typeof vi.fn>; findMany: ReturnType<typeof vi.fn> };
    $queryRaw: ReturnType<typeof vi.fn>;
  };
  let mockSearchRepo: {
    indexDocument: ReturnType<typeof vi.fn>;
    removeDocument: ReturnType<typeof vi.fn>;
    dropCollection: ReturnType<typeof vi.fn>;
    ensureCollection: ReturnType<typeof vi.fn>;
    indexDocuments: ReturnType<typeof vi.fn>;
  };
  let mockLogger: {
    log: ReturnType<typeof vi.fn>;
    warn: ReturnType<typeof vi.fn>;
    error: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockPrisma = {
      listing: {
        findUnique: vi.fn(),
        findMany: vi.fn(),
      },
      $queryRaw: vi.fn(),
    };
    mockSearchRepo = {
      indexDocument: vi.fn(),
      removeDocument: vi.fn(),
      dropCollection: vi.fn(),
      ensureCollection: vi.fn(),
      indexDocuments: vi.fn(),
    };
    mockLogger = {
      log: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    service = new ListingIndexerService(
      mockPrisma as any,
      mockSearchRepo as any,
      mockLogger as any,
    );
  });

  it('indexes an active listing when found with coordinates', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue(mockListing);
    mockPrisma.$queryRaw.mockResolvedValue([{ lat: 10.776, lng: 106.700 }]);
    mockSearchRepo.indexDocument.mockResolvedValue(undefined);

    await service.indexListing('listing-1');

    expect(mockPrisma.listing.findUnique).toHaveBeenCalledWith(
      expect.objectContaining({ where: { id: 'listing-1' } }),
    );
    expect(mockSearchRepo.indexDocument).toHaveBeenCalledWith(
      expect.objectContaining({ id: 'listing-1', status: 'ACTIVE' }),
    );
  });

  it('skips indexing when listing status is not ACTIVE', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue({ ...mockListing, status: 'INACTIVE' });
    mockPrisma.$queryRaw.mockResolvedValue([{ lat: 10.776, lng: 106.700 }]);

    await service.indexListing('listing-1');

    expect(mockSearchRepo.indexDocument).not.toHaveBeenCalled();
    expect(mockLogger.warn).toHaveBeenCalled();
  });

  it('skips indexing when listing is not found', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue(null);

    await service.indexListing('listing-99');

    expect(mockSearchRepo.indexDocument).not.toHaveBeenCalled();
    expect(mockLogger.warn).toHaveBeenCalled();
  });

  it('calls searchRepo.removeDocument with the listing id', async () => {
    mockSearchRepo.removeDocument.mockResolvedValue(undefined);

    await service.removeListing('listing-1');

    expect(mockSearchRepo.removeDocument).toHaveBeenCalledWith('listing-1');
  });

  it('drops and recreates collection then batches documents during reindexAll', async () => {
    mockSearchRepo.dropCollection.mockResolvedValue(undefined);
    mockSearchRepo.ensureCollection.mockResolvedValue(undefined);
    mockSearchRepo.indexDocuments.mockResolvedValue(undefined);

    const batchListings = [mockListing, { ...mockListing, id: 'listing-2' }];
    mockPrisma.listing.findMany
      .mockResolvedValueOnce(batchListings)
      .mockResolvedValueOnce([]);
    mockPrisma.$queryRaw.mockResolvedValue([{ id: 'prop-1', lat: 10.776, lng: 106.700 }]);

    const result = await service.reindexAll();

    expect(mockSearchRepo.dropCollection).toHaveBeenCalledOnce();
    expect(mockSearchRepo.ensureCollection).toHaveBeenCalledOnce();
    expect(mockSearchRepo.indexDocuments).toHaveBeenCalledOnce();
    expect(result.indexed).toBe(2);
    expect(result.total).toBe(2);
  });

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

    const result = await service.fetchListingDocumentById('missing-id');

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

  it('fetchListingDocumentById returns a complete document with coordinates', async () => {
    mockPrisma.listing.findUnique.mockResolvedValue(mockListing);
    mockPrisma.$queryRaw.mockResolvedValue([{ lat: 10.776, lng: 106.700 }]);

    const result = await service.fetchListingDocumentById('listing-1');

    expect(result).not.toBeNull();
    expect(result!.id).toBe('listing-1');
    expect(result!.propertyId).toBe('prop-1');
    expect(result!.title).toBe('Test');
    expect(result!.priceVND).toBe(5000000000);
    expect(result!.location).toEqual([10.776, 106.700]);
    expect(result!.amenities).toEqual(['parking']);

    // Verify normalized fields are populated
    expect(result!.titleNormalized).toBe('test');
    expect(result!.descriptionNormalized).toBe('desc');
    expect(result!.addressNormalized).toBe('123 street');
    expect(result!.wardNormalized).toBe('ward 1');
    expect(result!.districtNormalized).toBe('district 1');
    expect(result!.cityNormalized).toBe('hcmc');
    expect(result!.projectNameNormalized).toBeNull();
  });

  it('normalizes Vietnamese diacritics in indexed fields', async () => {
    const vietnameseListing = {
      ...mockListing,
      property: {
        ...mockListing.property,
        title: 'Căn hộ cao cấp',
        description: 'Biệt thự đẹp',
        address: '123 Đường Nguyễn Huệ',
        ward: 'Phường Bến Nghé',
        district: 'Quận 1',
        city: 'Hồ Chí Minh',
        projectName: 'Vinhomes Bason',
      },
    };
    mockPrisma.listing.findUnique.mockResolvedValue(vietnameseListing);
    mockPrisma.$queryRaw.mockResolvedValue([{ lat: 10.776, lng: 106.700 }]);

    const result = await service.fetchListingDocumentById('listing-1');

    expect(result!.titleNormalized).toBe('can ho cao cap');
    expect(result!.descriptionNormalized).toBe('biet thu dep');
    expect(result!.addressNormalized).toBe('123 duong nguyen hue');
    expect(result!.wardNormalized).toBe('phuong ben nghe');
    expect(result!.districtNormalized).toBe('quan 1');
    expect(result!.cityNormalized).toBe('ho chi minh');
    expect(result!.projectNameNormalized).toBe('vinhomes bason');
  });
});
