import { type CacheService } from '@modules/shared/infrastructure/cache.service';
import { type ISearchRepository, type SearchResult } from '../../domain/repositories/search.repository';
import { SearchPropertiesHandler } from '../queries/search-properties/search-properties.handler';
import { SearchPropertiesQuery } from '../queries/search-properties/search-properties.query';

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

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

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

  it('searches with basic query', async () => {
    const expected = createMockSearchResult({ totalFound: 5 });
    mockSearchRepo.search.mockResolvedValue(expected);

    const query = new SearchPropertiesQuery('Quận 7 căn hộ');
    const result = await handler.execute(query);

    expect(result).toEqual(expected);
    expect(mockSearchRepo.search).toHaveBeenCalledWith(
      expect.objectContaining({
        query: 'Quận 7 căn hộ',
        filterBy: 'status:=ACTIVE',
      }),
    );
  });

  it('applies all filters correctly', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(
      'nhà phố', 'HOUSE', 'SALE', 2000000000, 5000000000,
      50, 200, 3, 'Quận 7', 'Hồ Chí Minh', 'price_asc', 1, 20,
    );
    await handler.execute(query);

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).toContain('status:=ACTIVE');
    expect(searchCall.filterBy).toContain('propertyType:=HOUSE');
    expect(searchCall.filterBy).toContain('transactionType:=SALE');
    expect(searchCall.filterBy).toContain('priceVND:[2000000000..5000000000]');
    expect(searchCall.filterBy).toContain('areaM2:[50..200]');
    expect(searchCall.filterBy).toContain('bedrooms:>=3');
    expect(searchCall.filterBy).toContain('district:=Quận 7');
    expect(searchCall.filterBy).toContain('city:=Hồ Chí Minh');
  });

  it('applies only priceMin when priceMax is undefined', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(undefined, undefined, undefined, 1000000000);
    await handler.execute(query);

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

  it('applies only priceMax when priceMin is undefined', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(undefined, undefined, undefined, undefined, 5000000000);
    await handler.execute(query);

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

  it('applies only areaMin when areaMax is undefined', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(undefined, undefined, undefined, undefined, undefined, 50);
    await handler.execute(query);

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

  it('applies only areaMax when areaMin is undefined', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(undefined, undefined, undefined, undefined, undefined, undefined, 200);
    await handler.execute(query);

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

  it('applies featured=true filter as isFeatured:=1', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(
      undefined, undefined, undefined, undefined, undefined,
      undefined, undefined, undefined, undefined, undefined,
      undefined, undefined, undefined, true,
    );
    await handler.execute(query);

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

  it('applies featured=false filter as isFeatured:=0', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    const query = new SearchPropertiesQuery(
      undefined, undefined, undefined, undefined, undefined,
      undefined, undefined, undefined, undefined, undefined,
      undefined, undefined, undefined, false,
    );
    await handler.execute(query);

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

  it('omits isFeatured filter when featured is undefined', async () => {
    mockSearchRepo.search.mockResolvedValue(createMockSearchResult());

    await handler.execute(new SearchPropertiesQuery('anything'));

    const searchCall = mockSearchRepo.search.mock.calls[0]![0];
    expect(searchCall.filterBy).not.toContain('isFeatured');
  });
});
