import { Inject, InternalServerErrorException } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { CacheService, CachePrefix, CacheTTL, DomainException, LoggerService } from '@modules/shared';
import {
  SEARCH_REPOSITORY,
  type ISearchRepository,
  type SearchResult,
} from '../../../domain/repositories/search.repository';
import { GeoSearchQuery } from './geo-search.query';

@QueryHandler(GeoSearchQuery)
export class GeoSearchHandler implements IQueryHandler<GeoSearchQuery> {
  constructor(
    @Inject(SEARCH_REPOSITORY) private readonly searchRepo: ISearchRepository,
    private readonly cache: CacheService,
    private readonly logger: LoggerService,
  ) {}

  async execute(query: GeoSearchQuery): Promise<SearchResult> {
    try {
      const cacheKey = CacheService.buildKey(
        CachePrefix.GEO_SEARCH,
        `${query.lat}_${query.lng}_${query.radiusKm}`,
        query.propertyType,
        query.transactionType,
        query.priceMin,
        query.priceMax,
        query.sortBy,
        query.page,
        query.perPage,
      );

      return this.cache.getOrSet(
        cacheKey,
        async () => {
          const filters: string[] = ['status:=ACTIVE'];

          if (query.propertyType) {
            filters.push(`propertyType:=${query.propertyType}`);
          }
          if (query.transactionType) {
            filters.push(`transactionType:=${query.transactionType}`);
          }
          if (query.priceMin !== undefined && query.priceMax !== undefined) {
            filters.push(`priceVND:[${query.priceMin}..${query.priceMax}]`);
          } else if (query.priceMin !== undefined) {
            filters.push(`priceVND:>=${query.priceMin}`);
          } else if (query.priceMax !== undefined) {
            filters.push(`priceVND:<=${query.priceMax}`);
          }

          return this.searchRepo.search({
            query: '*',
            filterBy: filters.join(' && '),
            sortBy: query.sortBy,
            page: query.page,
            perPage: query.perPage,
            geoPoint: { lat: query.lat, lng: query.lng },
            geoRadiusKm: Math.min(query.radiusKm, 100),
          });
        },
        CacheTTL.SEARCH_RESULTS,
        'geo_search',
      );
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to execute geo search: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể thực hiện tìm kiếm theo vị trí địa lý');
    }
  }
}
