import { type Prisma } from '@prisma/client';
import { type PrismaService } from '@modules/shared';
import { type ListingDetailData, type ListingSearchItem, type ListingSellerItem } from '../../domain/repositories/listing-read.dto';
import { type ListingSearchParams, type PaginatedResult } from '../../domain/repositories/listing.repository';

export async function findByIdWithProperty(
  prisma: PrismaService,
  id: string,
): Promise<ListingDetailData | null> {
  const listing = await prisma.listing.findUnique({
    where: { id },
    include: {
      property: {
        include: {
          media: { orderBy: { order: 'asc' }, take: 10 },
        },
      },
      seller: { select: { id: true, fullName: true, phone: true } },
      agent: { select: { id: true, userId: true, agency: true } },
    },
  });

  if (!listing) return null;

  // Extract lat/lng from PostGIS geometry via raw SQL
  const geoRows = await prisma.$queryRaw<{ latitude: number; longitude: number }[]>`
    SELECT
      ST_Y("location"::geometry) AS latitude,
      ST_X("location"::geometry) AS longitude
    FROM "Property"
    WHERE "id" = ${listing.property.id}
    LIMIT 1
  `;
  // location is NOT NULL in the database — geo extraction always succeeds for existing properties
  const geo = geoRows[0]!;

  const now = new Date();
  return {
    id: listing.id,
    status: listing.status,
    transactionType: listing.transactionType,
    priceVND: listing.priceVND.toString(),
    pricePerM2: listing.pricePerM2,
    rentPriceMonthly: listing.rentPriceMonthly?.toString() ?? null,
    commissionPct: listing.commissionPct,
    viewCount: listing.viewCount,
    saveCount: listing.saveCount,
    inquiryCount: listing.inquiryCount,
    isFeatured: listing.featuredUntil != null && listing.featuredUntil > now,
    featuredUntil: listing.featuredUntil?.toISOString() ?? null,
    publishedAt: listing.publishedAt?.toISOString() ?? null,
    createdAt: listing.createdAt.toISOString(),
    property: {
      id: listing.property.id,
      propertyType: listing.property.propertyType,
      title: listing.property.title,
      description: listing.property.description,
      address: listing.property.address,
      ward: listing.property.ward,
      district: listing.property.district,
      city: listing.property.city,
      latitude: geo.latitude,
      longitude: geo.longitude,
      areaM2: listing.property.areaM2,
      bedrooms: listing.property.bedrooms,
      bathrooms: listing.property.bathrooms,
      floors: listing.property.floors,
      direction: listing.property.direction,
      yearBuilt: listing.property.yearBuilt,
      legalStatus: listing.property.legalStatus,
      amenities: listing.property.amenities,
      projectName: listing.property.projectName,
      media: listing.property.media.map((m) => ({
        id: m.id,
        url: m.url,
        type: m.type,
        order: m.order,
        caption: m.caption,
      })),
    },
    seller: listing.seller,
    agent: listing.agent,
  };
}

export async function searchListings(
  prisma: PrismaService,
  params: ListingSearchParams,
): Promise<PaginatedResult<ListingSearchItem>> {
  const page = params.page ?? 1;
  const limit = Math.min(params.limit ?? 20, 100);
  const skip = (page - 1) * limit;

  const where: Prisma.ListingWhereInput = {};

  if (params.status) where.status = params.status;
  if (params.transactionType) where.transactionType = params.transactionType;
  if (params.minPrice || params.maxPrice) {
    where.priceVND = {};
    if (params.minPrice) where.priceVND.gte = params.minPrice;
    if (params.maxPrice) where.priceVND.lte = params.maxPrice;
  }

  if (params.propertyType || params.city || params.district || params.minArea || params.maxArea || params.bedrooms) {
    where.property = {};
    if (params.propertyType) where.property.propertyType = params.propertyType;
    if (params.city) where.property.city = params.city;
    if (params.district) where.property.district = params.district;
    if (params.minArea || params.maxArea) {
      where.property.areaM2 = {};
      if (params.minArea) where.property.areaM2.gte = params.minArea;
      if (params.maxArea) where.property.areaM2.lte = params.maxArea;
    }
    if (params.bedrooms) where.property.bedrooms = { gte: params.bedrooms };
  }

  const [data, total] = await Promise.all([
    prisma.listing.findMany({
      where,
      skip,
      take: limit,
      orderBy: [
        { featuredUntil: { sort: 'desc', nulls: 'last' } },
        { createdAt: 'desc' },
      ],
      include: {
        property: {
          include: {
            media: { orderBy: { order: 'asc' }, take: 5 },
          },
        },
        seller: { select: { id: true, fullName: true } },
      },
    }),
    prisma.listing.count({ where }),
  ]);

  // Batch-extract lat/lng for all properties in the result set
  const propertyIds = data.map((l) => l.property.id);
  const geoMap = new Map<string, { latitude: number; longitude: number }>();
  if (propertyIds.length > 0) {
    const geoRows = await prisma.$queryRaw<{ id: string; latitude: number; longitude: number }[]>`
      SELECT
        "id",
        ST_Y("location"::geometry) AS latitude,
        ST_X("location"::geometry) AS longitude
      FROM "Property"
      WHERE "id" = ANY(${propertyIds})
    `;
    for (const row of geoRows) {
      geoMap.set(row.id, { latitude: row.latitude, longitude: row.longitude });
    }
  }

  const now = new Date();
  return {
    data: data.map((listing) => {
      // location is NOT NULL — every property in the result set has geo data
      const geo = geoMap.get(listing.property.id)!;
      return {
        id: listing.id,
        status: listing.status,
        transactionType: listing.transactionType,
        priceVND: listing.priceVND.toString(),
        pricePerM2: listing.pricePerM2,
        isFeatured: listing.featuredUntil != null && listing.featuredUntil > now,
        featuredUntil: listing.featuredUntil?.toISOString() ?? null,
        viewCount: listing.viewCount,
        publishedAt: listing.publishedAt?.toISOString() ?? null,
        property: {
          id: listing.property.id,
          propertyType: listing.property.propertyType,
          title: listing.property.title,
          address: listing.property.address,
          district: listing.property.district,
          city: listing.property.city,
          latitude: geo.latitude,
          longitude: geo.longitude,
          areaM2: listing.property.areaM2,
          bedrooms: listing.property.bedrooms,
          bathrooms: listing.property.bathrooms,
          thumbnail: listing.property.media[0]?.url ?? null,
          media: listing.property.media.map((m) => ({
            id: m.id,
            url: m.url,
            type: m.type,
            order: m.order,
            caption: m.caption,
          })),
        },
        seller: listing.seller,
      };
    }),
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
  };
}

export async function findBySellerIdQuery(
  prisma: PrismaService,
  sellerId: string,
  page: number,
  limit: number,
): Promise<PaginatedResult<ListingSellerItem>> {
  const skip = (page - 1) * limit;
  const where: Prisma.ListingWhereInput = { sellerId };

  const [data, total] = await Promise.all([
    prisma.listing.findMany({
      where,
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
      include: {
        property: {
          include: { media: { orderBy: { order: 'asc' }, take: 1 } },
        },
      },
    }),
    prisma.listing.count({ where }),
  ]);

  const sellerNow = new Date();
  return {
    data: data.map((listing) => ({
      id: listing.id,
      status: listing.status,
      transactionType: listing.transactionType,
      priceVND: listing.priceVND.toString(),
      isFeatured: listing.featuredUntil != null && listing.featuredUntil > sellerNow,
      featuredUntil: listing.featuredUntil?.toISOString() ?? null,
      property: {
        id: listing.property.id,
        title: listing.property.title,
        district: listing.property.district,
        city: listing.property.city,
        areaM2: listing.property.areaM2,
        thumbnail: listing.property.media[0]?.url ?? null,
        media: listing.property.media.map((m) => ({
          id: m.id,
          url: m.url,
          type: m.type,
          order: m.order,
          caption: m.caption,
        })),
      },
    })),
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
  };
}
