import { Injectable } from '@nestjs/common';
import { type PrismaService, type LoggerService } from '@modules/shared';
import {
  type INeighborhoodScoreService,
  type NeighborhoodScoreResult,
} from '../../domain/services/neighborhood-score.service';

/**
 * Scoring weights for each POI category.
 * Sum = 100 (total score is 0–100 weighted average).
 */
const CATEGORY_WEIGHTS = {
  education: 20,
  healthcare: 20,
  transport: 20,
  shopping: 15,
  greenery: 15,
  safety: 10,
};

/** POI types grouped by scoring category. */
const CATEGORY_POI_TYPES: Record<string, string[]> = {
  education: ['SCHOOL', 'UNIVERSITY'],
  healthcare: ['HOSPITAL', 'CLINIC'],
  transport: ['METRO_STATION', 'BUS_STOP'],
  shopping: ['MALL', 'MARKET', 'SUPERMARKET'],
  greenery: ['PARK'],
  safety: ['POLICE_STATION', 'FIRE_STATION'],
};

/** Max count per category that yields a 10/10 score. */
const MAX_COUNTS: Record<string, number> = {
  education: 15,
  healthcare: 8,
  transport: 12,
  shopping: 10,
  greenery: 6,
  safety: 4,
};

@Injectable()
export class NeighborhoodScoreServiceImpl implements INeighborhoodScoreService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
  ) {}

  async getScore(district: string, city: string): Promise<NeighborhoodScoreResult | null> {
    const existing = await this.prisma.neighborhoodScore.findUnique({
      where: { district_city: { district, city } },
    });

    if (!existing) return null;

    return {
      district: existing.district,
      city: existing.city,
      educationScore: existing.educationScore,
      healthcareScore: existing.healthcareScore,
      transportScore: existing.transportScore,
      shoppingScore: existing.shoppingScore,
      greeneryScore: existing.greeneryScore,
      safetyScore: existing.safetyScore,
      totalScore: existing.totalScore,
      poiCounts: existing.poiCounts as Record<string, number>,
      calculatedAt: existing.calculatedAt,
    };
  }

  async calculateAndSave(district: string, city: string): Promise<NeighborhoodScoreResult> {
    // Count POIs per category for this district
    const poiCounts: Record<string, number> = {};
    const categoryScores: Record<string, number> = {};

    for (const [category, poiTypes] of Object.entries(CATEGORY_POI_TYPES)) {
      const count = await this.prisma.pOI.count({
        where: {
          district,
          city,
          type: { in: poiTypes as any },
        },
      });

      poiCounts[category] = count;
      // Score 0–10: linear scale capped at MAX_COUNTS
      const maxCount = MAX_COUNTS[category]!;
      categoryScores[category] = Math.min(10, (count / maxCount) * 10);
    }

    // Weighted total score (0–100)
    const totalScore = Object.entries(CATEGORY_WEIGHTS).reduce((sum, [cat, weight]) => {
      return sum + (categoryScores[cat]! * weight) / 10;
    }, 0);

    const result = await this.prisma.neighborhoodScore.upsert({
      where: { district_city: { district, city } },
      create: {
        district,
        city,
        educationScore: categoryScores['education']!,
        healthcareScore: categoryScores['healthcare']!,
        transportScore: categoryScores['transport']!,
        shoppingScore: categoryScores['shopping']!,
        greeneryScore: categoryScores['greenery']!,
        safetyScore: categoryScores['safety']!,
        totalScore: Math.round(totalScore * 10) / 10,
        poiCounts,
        calculatedAt: new Date(),
      },
      update: {
        educationScore: categoryScores['education']!,
        healthcareScore: categoryScores['healthcare']!,
        transportScore: categoryScores['transport']!,
        shoppingScore: categoryScores['shopping']!,
        greeneryScore: categoryScores['greenery']!,
        safetyScore: categoryScores['safety']!,
        totalScore: Math.round(totalScore * 10) / 10,
        poiCounts,
        calculatedAt: new Date(),
      },
    });

    this.logger.log(
      `Neighborhood score calculated: ${district}, ${city} → total=${result.totalScore}`,
      'NeighborhoodScoreService',
    );

    return {
      district: result.district,
      city: result.city,
      educationScore: result.educationScore,
      healthcareScore: result.healthcareScore,
      transportScore: result.transportScore,
      shoppingScore: result.shoppingScore,
      greeneryScore: result.greeneryScore,
      safetyScore: result.safetyScore,
      totalScore: result.totalScore,
      poiCounts: result.poiCounts as Record<string, number>,
      calculatedAt: result.calculatedAt,
    };
  }
}
