import { Injectable } from '@nestjs/common';
import { type MarketIndex as PrismaMarketIndex, type PropertyType } from '@prisma/client';
import { type PrismaService } from '@modules/shared';
import { MarketIndexEntity, type MarketIndexProps } from '../../domain/entities/market-index.entity';
import {
  type IMarketIndexRepository,
  type MarketReportResult,
  type HeatmapDataPoint,
  type PriceTrendPoint,
  type DistrictStatsResult,
} from '../../domain/repositories/market-index.repository';

@Injectable()
export class PrismaMarketIndexRepository implements IMarketIndexRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<MarketIndexEntity | null> {
    const record = await this.prisma.marketIndex.findUnique({ where: { id } });
    return record ? this.toDomain(record) : null;
  }

  async findByKey(
    district: string,
    city: string,
    propertyType: PropertyType,
    period: string,
  ): Promise<MarketIndexEntity | null> {
    const record = await this.prisma.marketIndex.findUnique({
      where: {
        district_city_propertyType_period: { district, city, propertyType, period },
      },
    });
    return record ? this.toDomain(record) : null;
  }

  async save(entity: MarketIndexEntity): Promise<void> {
    await this.prisma.marketIndex.create({
      data: {
        id: entity.id,
        district: entity.district,
        city: entity.city,
        propertyType: entity.propertyType,
        period: entity.period,
        medianPrice: entity.medianPrice,
        avgPriceM2: entity.avgPriceM2,
        totalListings: entity.totalListings,
        daysOnMarket: entity.daysOnMarket,
        inventoryLevel: entity.inventoryLevel,
        absorptionRate: entity.absorptionRate,
        yoyChange: entity.yoyChange,
      },
    });
  }

  async update(entity: MarketIndexEntity): Promise<void> {
    await this.prisma.marketIndex.update({
      where: { id: entity.id },
      data: {
        medianPrice: entity.medianPrice,
        avgPriceM2: entity.avgPriceM2,
        totalListings: entity.totalListings,
        daysOnMarket: entity.daysOnMarket,
        inventoryLevel: entity.inventoryLevel,
        absorptionRate: entity.absorptionRate,
        yoyChange: entity.yoyChange,
      },
    });
  }

  async getMarketReport(
    city: string,
    period: string,
    propertyType?: PropertyType,
  ): Promise<MarketReportResult[]> {
    const where: Record<string, unknown> = { city, period };
    if (propertyType) where['propertyType'] = propertyType;

    const records = await this.prisma.marketIndex.findMany({
      where,
      orderBy: { district: 'asc' },
    });

    return records.map((r) => ({
      district: r.district,
      city: r.city,
      propertyType: r.propertyType,
      period: r.period,
      medianPrice: r.medianPrice.toString(),
      avgPriceM2: r.avgPriceM2,
      totalListings: r.totalListings,
      daysOnMarket: r.daysOnMarket,
      inventoryLevel: r.inventoryLevel,
      absorptionRate: r.absorptionRate,
      yoyChange: r.yoyChange,
    }));
  }

  async getHeatmap(city: string, period: string): Promise<HeatmapDataPoint[]> {
    const records = await this.prisma.marketIndex.findMany({
      where: { city, period },
      orderBy: { avgPriceM2: 'desc' },
    });

    const districtMap = new Map<string, { totalPrice: number; totalListings: number; count: number; medianPrices: bigint[] }>();

    for (const r of records) {
      const existing = districtMap.get(r.district);
      if (existing) {
        existing.totalPrice += r.avgPriceM2;
        existing.totalListings += r.totalListings;
        existing.count++;
        existing.medianPrices.push(r.medianPrice);
      } else {
        districtMap.set(r.district, {
          totalPrice: r.avgPriceM2,
          totalListings: r.totalListings,
          count: 1,
          medianPrices: [r.medianPrice],
        });
      }
    }

    return Array.from(districtMap.entries()).map(([district, data]) => ({
      district,
      city,
      avgPriceM2: data.totalPrice / data.count,
      totalListings: data.totalListings,
      medianPrice: (data.medianPrices[Math.floor(data.medianPrices.length / 2)] ?? 0).toString(),
    }));
  }

  async getPriceTrend(
    district: string,
    city: string,
    propertyType: PropertyType,
    periods: string[],
  ): Promise<PriceTrendPoint[]> {
    const records = await this.prisma.marketIndex.findMany({
      where: {
        district,
        city,
        propertyType,
        period: { in: periods },
      },
      orderBy: { period: 'asc' },
    });

    return records.map((r) => ({
      period: r.period,
      medianPrice: r.medianPrice.toString(),
      avgPriceM2: r.avgPriceM2,
      totalListings: r.totalListings,
    }));
  }

  async getDistrictStats(city: string, period: string): Promise<DistrictStatsResult[]> {
    const records = await this.prisma.marketIndex.findMany({
      where: { city, period },
      orderBy: [{ district: 'asc' }, { propertyType: 'asc' }],
    });

    return records.map((r) => ({
      district: r.district,
      city: r.city,
      propertyType: r.propertyType,
      medianPrice: r.medianPrice.toString(),
      avgPriceM2: r.avgPriceM2,
      totalListings: r.totalListings,
      daysOnMarket: r.daysOnMarket,
      inventoryLevel: r.inventoryLevel,
      absorptionRate: r.absorptionRate,
      yoyChange: r.yoyChange,
    }));
  }

  private toDomain(raw: PrismaMarketIndex): MarketIndexEntity {
    const props: MarketIndexProps = {
      district: raw.district,
      city: raw.city,
      propertyType: raw.propertyType,
      period: raw.period,
      medianPrice: raw.medianPrice,
      avgPriceM2: raw.avgPriceM2,
      totalListings: raw.totalListings,
      daysOnMarket: raw.daysOnMarket,
      inventoryLevel: raw.inventoryLevel,
      absorptionRate: raw.absorptionRate,
      yoyChange: raw.yoyChange,
    };

    return new MarketIndexEntity(raw.id, props, raw.createdAt);
  }
}
