import { Inject, Injectable } from '@nestjs/common';
import { PrismaService, LoggerService } from '@modules/shared';
import {
  type IAVMService,
  type AVMParams,
  type ValuationResult,
  type Comparable,
  type BatchValuationItem,
  type BatchValuationResult,
} from '../../domain/services/avm-service';
import {
  AI_SERVICE_CLIENT,
  type IAiServiceClient,
  type AiPredictRequest,
  type AiPredictV2Request,
} from './ai-service.client';

/** Map string risk buckets to the 0..1 float the Python service expects. */
const FLOOD_RISK_TO_SCORE: Record<string, number> = {
  NONE: 0,
  LOW: 0.33,
  MEDIUM: 0.66,
  HIGH: 1,
};
import { PrismaAVMService } from './prisma-avm.service';

/** Max concurrency for batch AI calls to avoid overloading the Python service. */
const BATCH_CONCURRENCY = 5;

@Injectable()
export class HttpAVMService implements IAVMService {
  constructor(
    @Inject(AI_SERVICE_CLIENT) private readonly aiClient: IAiServiceClient,
    private readonly fallback: PrismaAVMService,
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
  ) {}

  async estimateValue(params: AVMParams): Promise<ValuationResult> {
    try {
      return await this.estimateViaAi(params);
    } catch (err) {
      this.logger.warn(
        `AI AVM service unavailable, falling back to comparables-based estimation: ${(err as Error).message}`,
        'HttpAVMService',
      );
      return this.fallback.estimateValue(params);
    }
  }

  async getComparables(propertyId: string, radiusMeters: number): Promise<Comparable[]> {
    return this.fallback.getComparables(propertyId, radiusMeters);
  }

  async estimateBatch(items: BatchValuationItem[]): Promise<BatchValuationResult[]> {
    const results: BatchValuationResult[] = [];

    // Process in batches with limited concurrency
    for (let i = 0; i < items.length; i += BATCH_CONCURRENCY) {
      const chunk = items.slice(i, i + BATCH_CONCURRENCY);
      const chunkResults = await Promise.allSettled(
        chunk.map(async (item) => {
          const valuation = await this.estimateValue({ propertyId: item.propertyId });
          return { propertyId: item.propertyId, valuation } as BatchValuationResult;
        }),
      );

      for (let j = 0; j < chunkResults.length; j++) {
        const result = chunkResults[j]!;
        const item = chunk[j]!;
        if (result.status === 'fulfilled') {
          results.push(result.value);
        } else {
          this.logger.warn(
            `Batch valuation failed for property ${item.propertyId}: ${String(result.reason)}`,
            'HttpAVMService',
          );
          results.push({
            propertyId: item.propertyId,
            valuation: null,
            error: result.reason instanceof Error ? result.reason.message : 'Lỗi định giá',
          });
        }
      }
    }

    return results;
  }

  private async estimateViaAi(params: AVMParams): Promise<ValuationResult> {
    const propertyData = params.propertyId
      ? await this.getPropertyDetails(params.propertyId)
      : null;

    if (params.useV2) {
      return this.estimateViaAiV2(params, propertyData);
    }

    const request: AiPredictRequest = {
      area: params.areaM2 ?? propertyData?.areaM2 ?? 0,
      district: params.district ?? propertyData?.district ?? '',
      city: params.city ?? propertyData?.city ?? '',
      property_type: (params.propertyType ?? propertyData?.propertyType ?? 'house').toLowerCase(),
      bedrooms: params.bedrooms ?? propertyData?.bedrooms ?? 0,
      bathrooms: params.bathrooms ?? propertyData?.bathrooms ?? 0,
      floors: params.floors ?? propertyData?.floors ?? 0,
      frontage: params.frontage ?? 0,
      road_width: params.roadWidth ?? 0,
      year_built: params.yearBuilt ?? propertyData?.yearBuilt,
      has_legal_paper: params.hasLegalPaper ?? propertyData?.hasLegalPaper ?? true,
    };

    const aiResult = await this.aiClient.predict(request);

    // Also fetch comparables from the local PostGIS service for context
    let comparables: Comparable[] = [];
    try {
      if (params.propertyId) {
        comparables = await this.fallback.getComparables(params.propertyId, 2000);
      }
    } catch {
      // Comparables are supplementary — don't fail the valuation
    }

    return {
      estimatedPrice: Math.round(aiResult.estimated_price_vnd).toString(),
      confidence: aiResult.confidence,
      pricePerM2: Math.round(aiResult.price_per_m2),
      comparables,
      modelVersion: 'ai-service-v1.0',
    };
  }

  private async estimateViaAiV2(
    params: AVMParams,
    propertyData: Awaited<ReturnType<HttpAVMService['getPropertyDetails']>>,
  ): Promise<ValuationResult> {
    const yearBuilt = params.yearBuilt ?? propertyData?.yearBuilt ?? null;
    const now = new Date();

    const v2Request: AiPredictV2Request = {
      district: params.district ?? propertyData?.district ?? '',
      city: params.city ?? propertyData?.city ?? '',
      property_type: (params.propertyType ?? propertyData?.propertyType ?? 'house').toLowerCase(),
      area_m2: params.areaM2 ?? propertyData?.areaM2 ?? 0,
      distance_to_hospital_km: params.distanceToHospitalKm,
      distance_to_park_km: params.distanceToParkKm,
      distance_to_mall_km: params.distanceToMallKm,
      flood_zone_risk:
        params.floodZoneRisk != null ? FLOOD_RISK_TO_SCORE[params.floodZoneRisk] ?? 0 : undefined,
      rooms: params.bedrooms ?? propertyData?.bedrooms,
      total_floors: params.floors ?? propertyData?.floors,
      building_age_years: yearBuilt != null ? Math.max(0, now.getFullYear() - yearBuilt) : undefined,
      has_elevator: params.hasElevator,
      has_parking: params.hasParking,
      has_pool: params.hasPool,
      has_legal_paper: params.hasLegalPaper ?? propertyData?.hasLegalPaper ?? true,
      month: now.getMonth() + 1,
      quarter: Math.floor(now.getMonth() / 3) + 1,
      is_year_end: now.getMonth() >= 9,
    };

    const aiResult = await this.aiClient.predictV2(v2Request);

    let comparables: Comparable[] = [];
    try {
      if (params.propertyId) {
        comparables = await this.fallback.getComparables(params.propertyId, 2000);
      }
    } catch {
      // Supplementary — don't fail
    }

    return {
      estimatedPrice: Math.round(aiResult.estimated_price_vnd).toString(),
      confidence: aiResult.confidence,
      pricePerM2: Math.round(aiResult.price_per_m2_vnd),
      comparables,
      modelVersion: aiResult.model_version ?? 'ai-service-v2',
    };
  }

  private async getPropertyDetails(propertyId: string) {
    const row = await this.prisma.property.findUnique({
      where: { id: propertyId },
      select: {
        areaM2: true,
        district: true,
        city: true,
        propertyType: true,
        bedrooms: true,
        bathrooms: true,
        floors: true,
        yearBuilt: true,
        legalStatus: true,
      },
    });

    if (!row) return null;

    return {
      areaM2: row.areaM2,
      district: row.district,
      city: row.city,
      propertyType: row.propertyType,
      bedrooms: row.bedrooms ?? 0,
      bathrooms: row.bathrooms ?? 0,
      floors: row.floors ?? 0,
      yearBuilt: row.yearBuilt,
      hasLegalPaper: row.legalStatus === 'SO_DO' || row.legalStatus === 'SO_HONG',
    };
  }
}
