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

export interface PriceMoverItem {
  districtId: string;
  name: string;
  currentAvgPrice: number;
  previousAvgPrice: number;
  changePct: number;
  sampleSize: number;
}

export interface PriceMoversDto {
  direction: 'up' | 'down';
  period: string;
  level: string;
  limit: number;
  movers: PriceMoverItem[];
}

/** Days extracted from period string, e.g. '7d' → 7 */
function periodToDays(period: string): number {
  return parseInt(period.replace('d', ''), 10);
}

interface RawPriceMoverRow {
  district: string;
  current_avg: number | null;
  previous_avg: number | null;
  sample_size: bigint;
}

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

  @Cacheable({
    prefix: CachePrefix.PRICE_MOVERS,
    ttl: CacheTTL.PRICE_MOVERS,
    resource: 'price_movers',
    keyFrom: (query: unknown) => {
      const q = query as GetPriceMoversQuery;
      return [q.direction, q.period, String(q.limit), q.level];
    },
  })
  async execute(query: GetPriceMoversQuery): Promise<PriceMoversDto> {
    const { direction, period, limit, level } = query;

    try {
      const days = periodToDays(period);
      const now = new Date();
      const currentStart = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
      const previousStart = new Date(currentStart.getTime() - days * 24 * 60 * 60 * 1000);

      // Compare average listing price per district between current window and previous window.
      // Only include districts with at least 10 listings in the current window (min sample size).
      const rows = await this.prisma.$queryRaw<RawPriceMoverRow[]>`
        WITH current_window AS (
          SELECT
            p.district,
            AVG(l."priceVND") AS avg_price,
            COUNT(l.id) AS sample_size
          FROM "Listing" l
          INNER JOIN "Property" p ON p.id = l."propertyId"
          WHERE l."createdAt" >= ${currentStart}
            AND l.status = 'ACTIVE'
            AND l."priceVND" > 0
          GROUP BY p.district
          HAVING COUNT(l.id) >= 10
        ),
        previous_window AS (
          SELECT
            p.district,
            AVG(l."priceVND") AS avg_price
          FROM "Listing" l
          INNER JOIN "Property" p ON p.id = l."propertyId"
          WHERE l."createdAt" >= ${previousStart}
            AND l."createdAt" < ${currentStart}
            AND l.status = 'ACTIVE'
            AND l."priceVND" > 0
          GROUP BY p.district
        )
        SELECT
          c.district,
          c.avg_price AS current_avg,
          pr.avg_price AS previous_avg,
          c.sample_size
        FROM current_window c
        INNER JOIN previous_window pr ON pr.district = c.district
        WHERE pr.avg_price > 0
      `;

      // Compute changePct and sort by direction
      const computed = rows
        .map((r) => {
          const currentAvg = Number(r.current_avg);
          const previousAvg = Number(r.previous_avg);
          const changePct = ((currentAvg - previousAvg) / previousAvg) * 100;
          return {
            district: r.district,
            currentAvgPrice: Math.round(currentAvg),
            previousAvgPrice: Math.round(previousAvg),
            changePct: Math.round(changePct * 100) / 100,
            sampleSize: Number(r.sample_size),
          };
        })
        .filter((r) => (direction === 'up' ? r.changePct > 0 : r.changePct < 0));

      // Sort: 'up' → descending changePct, 'down' → ascending changePct
      computed.sort((a, b) =>
        direction === 'up' ? b.changePct - a.changePct : a.changePct - b.changePct,
      );

      const top = computed.slice(0, limit);

      const movers: PriceMoverItem[] = top.map((r) => ({
        districtId: r.district,
        name: r.district,
        currentAvgPrice: r.currentAvgPrice,
        previousAvgPrice: r.previousAvgPrice,
        changePct: r.changePct,
        sampleSize: r.sampleSize,
      }));

      return { direction, period, level, limit, movers };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to query price movers: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException(
        'Không thể truy vấn biến động giá. Vui lòng thử lại sau.',
      );
    }
  }
}
