import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { POIType } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { GetNearbyPOIsQuery } from './get-nearby-pois.query';

export type POICategory =
  | 'school'
  | 'hospital'
  | 'transit'
  | 'shopping'
  | 'restaurant'
  | 'park';

export interface NearbyPOIDto {
  id: string;
  name: string;
  type: POIType;
  category: POICategory;
  lat: number;
  lng: number;
  distance: number;
  address: string | null;
}

export interface NearbyPOIsResultDto {
  pois: NearbyPOIDto[];
  center: { lat: number; lng: number };
}

interface PoiRow {
  id: string;
  name: string;
  type: POIType;
  lat: number;
  lng: number;
  distance: number;
  address: string | null;
}

function mapTypeToCategory(type: POIType): POICategory {
  switch (type) {
    case 'SCHOOL':
    case 'UNIVERSITY':
      return 'school';
    case 'HOSPITAL':
    case 'CLINIC':
    case 'PHARMACY':
      return 'hospital';
    case 'METRO_STATION':
    case 'BUS_STOP':
      return 'transit';
    case 'MALL':
    case 'MARKET':
    case 'SUPERMARKET':
    case 'BANK':
    case 'ATM':
      return 'shopping';
    case 'RESTAURANT':
    case 'CAFE':
      return 'restaurant';
    case 'PARK':
      return 'park';
    default:
      return 'shopping';
  }
}

@QueryHandler(GetNearbyPOIsQuery)
export class GetNearbyPOIsHandler implements IQueryHandler<GetNearbyPOIsQuery> {
  constructor(private readonly prisma: PrismaService) {}

  async execute(query: GetNearbyPOIsQuery): Promise<NearbyPOIsResultDto> {
    const { lat, lng, radiusM, limit } = query;

    const rows = await this.prisma.$queryRawUnsafe<PoiRow[]>(
      `
      SELECT
        "id",
        "name",
        "type",
        ST_Y("location"::geometry) AS lat,
        ST_X("location"::geometry) AS lng,
        ST_Distance("location"::geography, ST_MakePoint($1, $2)::geography) AS distance,
        "address"
      FROM "POI"
      WHERE ST_DWithin("location"::geography, ST_MakePoint($1, $2)::geography, $3)
      ORDER BY distance ASC
      LIMIT $4
      `,
      lng,
      lat,
      radiusM,
      limit,
    );

    const pois: NearbyPOIDto[] = rows.map((row) => ({
      id: row.id,
      name: row.name,
      type: row.type,
      category: mapTypeToCategory(row.type),
      lat: Number(row.lat),
      lng: Number(row.lng),
      distance: Number(row.distance),
      address: row.address,
    }));

    return {
      pois,
      center: { lat, lng },
    };
  }
}
