import { type ISearchRepository, type SearchResult } from '../../domain/repositories/search.repository';
import { GeoSearchHandler } from '../queries/geo-search/geo-search.handler';
import { GeoSearchQuery } from '../queries/geo-search/geo-search.query';

function createMockSearchResult(overrides?: Partial<SearchResult>): SearchResult {
  return {
    hits: [],
    totalFound: 0,
    page: 1,
    perPage: 20,
    totalPages: 0,
    searchTimeMs: 5,
    ...overrides,
  };
}

describe('GeoSearchHandler', () => {
  let handler: GeoSearchHandler;
  let mockSearchRepo: { [K in keyof ISearchRepository]: ReturnType<typeof vi.fn> };
  let mockCache: {
    getOrSet: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    mockSearchRepo = {
      indexDocument: vi.fn(),
      indexDocuments: vi.fn(),
      removeDocument: vi.fn(),
      search: vi.fn(),
      ensureCollection: vi.fn(),
      dropCollection: vi.fn(),
    };
    mockCache = {
      getOrSet: vi.fn().mockImplementation((_key: string, loader: () => Promise<unknown>) => loader()),
    };
    handler = new GeoSearchHandler(mockSearchRepo as any, mockCache as any);
  });

  it('performs geo search with basic parameters', async () => {
    const expected = createMockSearchResult({ totalFound: 10 });
    mockSearchRepo.search.mockResolvedValue(expected);

    const query = new GeoSearchQuery(10.7769, 106.7009, 5);
    const result = await handler.execute(query);

    expect(result).toEqual(expected);
    expect(mockSearchRepo.search).toHaveBeenCalledWith(
      expect.objectContaining({
        query: '*',
        geoPoint: { lat: 10.7769, lng: 106.7009 },
        geoRadiusKm: 5,
      }),
    );
  });

  it('uses cache with correct TTL and resource label', async () => {
    const expected = createMockSearchResult({ totalFound: 5 });
    mockSearchRepo.search.mockResolvedValue(expected);

    const query = new GeoSearchQuery(10.7769, 106.7009, 5);
    await handler.execute(query);

    expect(mockCache.getOrSet).toHaveBeenCalledWith(
      expect.stringContaining('cache:geo_search:'),
      expect.any(Function),
      120,
      'geo_search',
    );
  });

  it('caps radius at 100km', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new GeoSearchQuery(10.7769, 106.7009, 200);
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.geoRadiusKm).toBe(100);
  });

  it('applies property and transaction type filters', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new GeoSearchQuery(10.7769, 106.7009, 10, 'APARTMENT', 'RENT');
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).toContain('propertyType:=APARTMENT');
    expect(searchCall.filterBy).toContain('transactionType:=RENT');
  });

  it('applies price range filter', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new GeoSearchQuery(10.7769, 106.7009, 5, undefined, undefined, 1000000000, 5000000000);
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).toContain('priceVND:[1000000000..5000000000]');
  });

  it('applies only priceMin filter', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new GeoSearchQuery(10.7769, 106.7009, 5, undefined, undefined, 2000000000);
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).toContain('priceVND:>=2000000000');
  });

  it('applies only priceMax filter', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new GeoSearchQuery(10.7769, 106.7009, 5, undefined, undefined, undefined, 3000000000);
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).toContain('priceVND:<=3000000000');
  });

  it('returns cached result without calling search repo', async () => {
    const cachedResult = createMockSearchResult({ totalFound: 42 });
    mockCache.getOrSet.mockResolvedValue(cachedResult);

    const query = new GeoSearchQuery(10.7769, 106.7009, 5);
    const result = await handler.execute(query);

    expect(result).toEqual(cachedResult);
    expect(mockSearchRepo.search).not.toHaveBeenCalled();
  });
});
