import axios from "axios";
import pool from "../config/db";
import { RowDataPacket } from "mysql2";

interface GeoResult {
  lat: number;
  lng: number;
  source?: string;
}

// Coordenadas exactas de centros de municipio — usadas para detectar fallback genérico
const MUNI_CENTERS: GeoResult[] = [
  { lat: 39.6292, lng: -0.5979 }, // Llíria
  { lat: 39.6736, lng: -0.5469 }, // Marines
  { lat: 39.6948, lng: -0.5224 }, // Olocau
  { lat: 39.6509, lng: -0.4917 }, // Náquera
  { lat: 39.5945, lng: -0.4680 }, // Bétera
  { lat: 39.5763, lng: -0.6262 }, // Villamarchante
  { lat: 39.6030, lng: -0.5720 }, // Benaguasil
  { lat: 39.6126, lng: -0.5396 }, // La Pobla de Vallbona
  { lat: 39.5629, lng: -0.4975 }, // L'Eliana
  { lat: 39.5448, lng: -0.5536 }, // Riba-roja de Túria
  { lat: 39.6665, lng: -0.4706 }, // Casinos
];

/** Jitter aleatorio ±radio_deg (≈0.002° ~ 200m) para evitar puntos apilados */
function jitter(val: number, range = 0.003): number {
  return val + (Math.random() - 0.5) * 2 * range;
}

/** True si las coordenadas coinciden exactamente con un centro de municipio conocido */
function isMuniCenter(lat: number, lng: number): boolean {
  return MUNI_CENTERS.some(c => Math.abs(c.lat - lat) < 0.0001 && Math.abs(c.lng - lng) < 0.0001);
}

/** Normaliza nombre de municipio para búsquedas: minúsculas, sin tildes básicas */
function normalizeMuniName(name: string): string {
  return name.toLowerCase().trim()
    .replace(/á/g, "a").replace(/é/g, "e").replace(/í/g, "i")
    .replace(/ó/g, "o").replace(/ú/g, "u").replace(/ü/g, "u")
    .replace(/à/g, "a").replace(/è/g, "e").replace(/ï/g, "i")
    .replace(/ò/g, "o").replace(/ñ/g, "n").replace(/ç/g, "c")
    .replace(/l['']l/g, "ll").replace(/[''']/g, "");
}

export class GeocodingService {
  private readonly nominatimUrl = "https://nominatim.openstreetmap.org/search";

  /**
   * Geocodifica una dirección completa usando Nominatim.
   * Lanza error si no encuentra resultado (NO usa Lliria como fallback).
   */
  async getCoordinates(address: string): Promise<GeoResult> {
    const response = await axios.get(this.nominatimUrl, {
      params: { q: address, format: "json", limit: 1, addressdetails: 1 },
      headers: { "User-Agent": "LliriaPropertiesScraper/1.0" },
      timeout: 8000,
    });
    if (response.data && response.data.length > 0) {
      const lat = parseFloat(response.data[0].lat);
      const lng = parseFloat(response.data[0].lon);
      // Si cae exactamente en el centro de un municipio, aplicar jitter pequeño
      if (isMuniCenter(lat, lng)) {
        return { lat: jitter(lat), lng: jitter(lng), source: "nominatim-center" };
      }
      return { lat, lng, source: "nominatim" };
    }
    throw new Error(`No geocoding result for: ${address}`);
  }

  /**
   * Geocodifica un nombre de municipio.
   * 1. Busca en tabla municipalities_es (7140 municipios de España)
   * 2. Fallback a mapa local hardcoded
   * 3. Fallback a Nominatim
   * Devuelve null si no encuentra nada (NO usa Lliria por defecto).
   */
  async geocodeMunicipality(name: string): Promise<GeoResult | null> {
    if (!name) return null;

    // 1. Tabla municipalities_es (BD local)
    try {
      const key = normalizeMuniName(name);
      const [rows] = await pool.query<RowDataPacket[]>(
        `SELECT lat, lng FROM municipalities_es
         WHERE ascii_name LIKE ? OR name LIKE ?
         ORDER BY population DESC LIMIT 1`,
        [`%${key}%`, `%${name}%`]
      );
      if (rows.length > 0 && rows[0].lat) {
        const { lat, lng } = rows[0];
        return { lat: jitter(parseFloat(lat)), lng: jitter(parseFloat(lng)), source: "db" };
      }
    } catch (_dbErr) { /* tabla puede no existir */ }

    // 2. Mapa local hardcoded (zonas del Camp de Túria + alrededores)
    const KNOWN: Record<string, [number, number]> = {
      "lliria": [39.6292, -0.5979],
      "marines": [39.6736, -0.5469],
      "olocau": [39.6948, -0.5224],
      "naquera": [39.6509, -0.4917],
      "betera": [39.5945, -0.4680],
      "villamarchante": [39.5763, -0.6262],
      "benaguasil": [39.6030, -0.5720],
      "pobla de vallbona": [39.6126, -0.5396],
      "la pobla de vallbona": [39.6126, -0.5396],
      "eliana": [39.5629, -0.4975],
      "l eliana": [39.5629, -0.4975],
      "riba-roja": [39.5448, -0.5536],
      "riba roja": [39.5448, -0.5536],
      "riba-roja de turia": [39.5448, -0.5536],
      "casinos": [39.6665, -0.4706],
      "pedralba": [39.6340, -0.7137],
      "cheste": [39.4871, -0.7020],
      "chiva": [39.4701, -0.7155],
      "requena": [39.4885, -1.0983],
      "sagunto": [39.6828, -0.2782],
      "valencia": [39.4699, -0.3763],
    };
    const normalizedKey = normalizeMuniName(name);
    if (KNOWN[normalizedKey]) {
      const [lat, lng] = KNOWN[normalizedKey];
      return { lat: jitter(lat), lng: jitter(lng), source: "hardcoded" };
    }

    // 3. Fallback a Nominatim para el municipio
    try {
      const geo = await this.getCoordinates(`${name}, Valencia, España`);
      return geo;
    } catch {
      try {
        const geo = await this.getCoordinates(`${name}, España`);
        return geo;
      } catch {
        return null; // No se encontró — NO devolver Lliria por defecto
      }
    }
  }
}


