"""
Geocoding Service
Mengkonversi nama RW/Kelurahan/Kecamatan → koordinat lat/lng
menggunakan Nominatim (OpenStreetMap) — gratis, tanpa API key.

Strategi pencarian bertingkat:
1. "RW {rw}, {kelurahan}, {kecamatan}, Jakarta"
2. "{kelurahan}, {kecamatan}, Jakarta"
3. "{kecamatan}, Jakarta"

Hasil di-cache per kombinasi (kelurahan+kecamatan+rw) agar
tidak memanggil API berulang untuk RW yang sama.
"""

import time
import urllib.request
import urllib.parse
import json
import logging

logger = logging.getLogger(__name__)

# Cache in-memory selama proses berjalan
_geocode_cache = {}

NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"
HEADERS = {
    "User-Agent": "SiBANDA-App/1.0 (Sistem Bantuan Daerah; contact@sibanda.local)",
    "Accept-Language": "id,en",
}


def _nominatim_query(query: str) -> tuple[float, float] | None:
    """
    Kirim query ke Nominatim, return (lat, lng) atau None.
    Rate limit: 1 request/detik sesuai ToS Nominatim.
    """
    params = urllib.parse.urlencode({
        "q": query,
        "format": "json",
        "limit": 1,
        "countrycodes": "id",
    })
    url = f"{NOMINATIM_URL}?{params}"

    try:
        req = urllib.request.Request(url, headers=HEADERS)
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read())
        if data:
            return float(data[0]["lat"]), float(data[0]["lon"])
    except Exception as e:
        logger.warning(f"Nominatim error untuk '{query}': {e}")
    return None


def geocode_rw(rw: str, kelurahan: str, kecamatan: str, kota: str = "Jakarta") -> dict | None:
    """
    Geocode satu RW. Return dict {lat, lng, query_used} atau None.
    Pakai cache supaya RW yang sama tidak di-query ulang.
    """
    # Normalisasi
    rw        = (rw or "").strip().lstrip("0") or "?"
    kelurahan = (kelurahan or "").strip().title()
    kecamatan = (kecamatan or "").strip().title()
    kota      = (kota or "Jakarta").strip().title()

    cache_key = f"{kelurahan}|{kecamatan}|{rw}"
    if cache_key in _geocode_cache:
        return _geocode_cache[cache_key]

    # Strategi bertingkat
    queries = [
        f"RW {rw}, {kelurahan}, {kecamatan}, {kota}, Indonesia",
        f"Kelurahan {kelurahan}, {kecamatan}, {kota}, Indonesia",
        f"{kecamatan}, {kota}, Indonesia",
    ]

    result = None
    for q in queries:
        coords = _nominatim_query(q)
        time.sleep(1.1)  # hormati rate limit Nominatim
        if coords:
            result = {"lat": coords[0], "lng": coords[1], "query_used": q}
            break

    _geocode_cache[cache_key] = result
    return result


def build_map_data(df_result, cluster_priority_map: dict) -> list[dict]:
    """
    Buat list marker per RW dari hasil clustering.

    Parameter:
        df_result         : DataFrame hasil clustering (sudah ada kolom cluster_label, prioritas)
        cluster_priority_map : {cluster_label: 'Prioritas Tinggi'|...}

    Return list marker:
    [
      {
        "rw": "005",
        "kelurahan": "Pulo Gebang",
        "kecamatan": "Cakung",
        "lat": -6.xxx,
        "lng": 106.xxx,
        "total": 120,
        "prioritas_tinggi": 40,
        "prioritas_sedang": 50,
        "prioritas_rendah": 30,
        "prioritas_dominan": "Prioritas Tinggi",
        "cluster_dominan": 0,
      },
      ...
    ]
    """
    from collections import defaultdict, Counter

    # Agregasi per RW
    rw_groups = defaultdict(lambda: {
        "rw": None, "kelurahan": None, "kecamatan": None, "kota": None,
        "total": 0,
        "Prioritas Tinggi": 0, "Prioritas Sedang": 0, "Prioritas Rendah": 0,
        "cluster_counter": Counter(),
    })

    for _, row in df_result.iterrows():
        rw        = str(row.get("rw") or "?").strip()
        kelurahan = str(row.get("kelurahan") or "").strip()
        kecamatan = str(row.get("kecamatan") or "").strip()
        kota      = str(row.get("kota") or "Jakarta").strip()
        prioritas = str(row.get("prioritas") or "Prioritas Sedang")
        cluster   = int(row.get("cluster_label", 0))

        key = f"{kelurahan}|{kecamatan}|{rw}"
        g   = rw_groups[key]
        g["rw"]        = rw
        g["kelurahan"] = kelurahan
        g["kecamatan"] = kecamatan
        g["kota"]      = kota
        g["total"]    += 1
        g[prioritas]  += 1
        g["cluster_counter"][cluster] += 1

    markers = []
    for key, g in rw_groups.items():
        # Geocode
        coords = geocode_rw(g["rw"], g["kelurahan"], g["kecamatan"], g["kota"])
        if not coords:
            logger.warning(f"Tidak dapat geocode RW {g['rw']} {g['kelurahan']}")
            continue

        # Cluster & prioritas dominan
        cluster_dominan  = g["cluster_counter"].most_common(1)[0][0]
        prioritas_dominan = cluster_priority_map.get(cluster_dominan, "Prioritas Sedang")

        markers.append({
            "rw"              : g["rw"],
            "kelurahan"       : g["kelurahan"],
            "kecamatan"       : g["kecamatan"],
            "lat"             : coords["lat"],
            "lng"             : coords["lng"],
            "total"           : g["total"],
            "prioritas_tinggi": g["Prioritas Tinggi"],
            "prioritas_sedang": g["Prioritas Sedang"],
            "prioritas_rendah": g["Prioritas Rendah"],
            "prioritas_dominan": prioritas_dominan,
            "cluster_dominan" : cluster_dominan,
            "query_used"      : coords.get("query_used", ""),
        })

    return markers