import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
import { DomainException, CacheService, CachePrefix, CacheTTL, Cacheable, LoggerService, PrismaService } from '@modules/shared';
import { GetTrendingAreasQuery } from './get-trending-areas.query';

export interface TrendingAreaItem {
  districtId: string;
  name: string;
  listings: number;
  inquiries: number;
  views: number;
  priceChangePct: number | null;
  scoreRank: number;
}

export interface TrendingAreasDto {
  period: number;
  level: string;
  limit: number;
  areas: TrendingAreaItem[];
}

interface RawDistrictRow {
  district: string;
  new_listings: bigint;
  inquiries: bigint;
  views: bigint;
}

@QueryHandler(GetTrendingAreasQuery)
export class GetTrendingAreasHandler implements IQueryHandler<GetTrendingAreasQuery> {
  constructor(
    private readonly prisma: PrismaService,
    private readonly cacheService: CacheService,
    private readonly logger: LoggerService,
  ) {}

  @Cacheable({
    prefix: CachePrefix.TRENDING_AREAS,
    ttl: CacheTTL.TRENDING_AREAS,
    resource: 'trending_areas',
    keyFrom: (query: unknown) => {
      const q = query as GetTrendingAreasQuery;
      return [String(q.period), String(q.limit), q.level];
    },
  })
  async execute(query: GetTrendingAreasQuery): Promise<TrendingAreasDto> {
    const { period, limit, level } = query;

    try {
      const since = new Date(Date.now() - period * 24 * 60 * 60 * 1000);

      // Aggregate new listings, inquiries, and views per district within the time window.
      // Listing.viewCount is a running total so we use it as a proxy for views.
      // Inquiry has createdAt that we can filter on.
      // New listings = listings created within the window.
      const rows = await this.prisma.$queryRaw<RawDistrictRow[]>`
        SELECT
          p.district,
          COUNT(DISTINCT l.id)  AS new_listings,
          COUNT(DISTINCT i.id)  AS inquiries,
          COALESCE(SUM(l."viewCount"), 0) AS views
        FROM "Listing" l
        INNER JOIN "Property" p ON p.id = l."propertyId"
        LEFT JOIN "Inquiry" i ON i."listingId" = l.id AND i."createdAt" >= ${since}
        WHERE l."createdAt" >= ${since}
          AND l.status = 'ACTIVE'
        GROUP BY p.district
      `;

      // Compute score for each district
      const scored = rows.map((r) => {
        const listings = Number(r.new_listings);
        const inquiries = Number(r.inquiries);
        const views = Number(r.views);
        const score = inquiries * 0.6 + views * 0.3 + listings * 0.1;
        return { district: r.district, listings, inquiries, views, score };
      });

      // Sort descending by score, take top `limit`
      scored.sort((a, b) => b.score - a.score);
      const top = scored.slice(0, limit);

      // Fetch price change (yoyChange) from MarketIndex for these districts
      const districts = top.map((r) => r.district);
      const marketIndexes = districts.length > 0
        ? await this.prisma.marketIndex.findMany({
            where: { district: { in: districts } },
            orderBy: { createdAt: 'desc' },
            select: { district: true, yoyChange: true },
          })
        : [];

      // Build a map district → most recent yoyChange
      const priceMap = new Map<string, number | null>();
      for (const mi of marketIndexes) {
        if (!priceMap.has(mi.district)) {
          priceMap.set(mi.district, mi.yoyChange);
        }
      }

      const areas: TrendingAreaItem[] = top.map((r, idx) => ({
        districtId: r.district,
        name: r.district,
        listings: r.listings,
        inquiries: r.inquiries,
        views: r.views,
        priceChangePct: priceMap.get(r.district) ?? null,
        scoreRank: idx + 1,
      }));

      return { period, level, limit, areas };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to truy vấn trending areas: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException(
        'Không thể truy vấn khu vực xu hướng. Vui lòng thử lại sau.',
      );
    }
  }
}
