#!/usr/bin/env python3
"""
LANGKAH 2 — Cleansing: mengubah panen mentah menjadi objek yang layak dicocokkan.

Corong penyaringan (urutannya penting):
  1. Buang duplikat persis (place_id sama).
  2. Buang duplikat dekat: nama yang sama setelah dinormalkan DAN jaraknya < 50 m.
  3. Buang usaha yang tutup permanen.
  4. Verifikasi wilayah: titik harus jatuh di dalam batas kecamatan (GeoJSON).
     Sekaligus mengisi kolom "kecamatan" untuk tiap objek.
  5. Saring kategori dengan TIGA keranjang, bukan dua:
       inti     — jelas objek PBJT
       abu-abu  — status pajaknya bergantung fakta lapangan (jangan dipaksa masuk/keluar)
       luar     — jelas bukan objek pajak daerah → dibuang

Kebutuhan: Python 3.9+, tanpa pustaka tambahan.

Contoh:
  python3 2_bersihkan.py --masukan contoh/contoh_panen.csv \
      --batas contoh/contoh_batas_kecamatan.geojson \
      --kamus kamus_kategori.csv --keluaran bersih.csv

  --batas berisi GeoJSON poligon kecamatan. Nama kecamatan dibaca dari properti
  yang ditentukan --properti-nama (default "kecamatan"). Batas wilayah administrasi
  resmi bisa diminta ke Bappeda atau diunduh dari portal Ina-Geoportal (BIG).
"""

import argparse
import csv
import json
import math
import re
import sys
from collections import Counter

JARAK_DUPLIKAT_M = 50


def normalkan_nama(nama):
    nama = nama.lower()
    nama = re.sub(r"[^a-z0-9 ]+", " ", nama)
    return re.sub(r"\s+", " ", nama).strip()


def jarak_meter(lat1, lng1, lat2, lng2):
    r = 6371000
    p1, p2 = math.radians(lat1), math.radians(lat2)
    dp = p2 - p1
    dl = math.radians(lng2 - lng1)
    a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
    return 2 * r * math.asin(math.sqrt(a))


def titik_dalam_cincin(x, y, cincin):
    """Ray casting. cincin = daftar [lng, lat]."""
    dalam = False
    j = len(cincin) - 1
    for i in range(len(cincin)):
        xi, yi = cincin[i][0], cincin[i][1]
        xj, yj = cincin[j][0], cincin[j][1]
        if (yi > y) != (yj > y) and x < (xj - xi) * (y - yi) / (yj - yi) + xi:
            dalam = not dalam
        j = i
    return dalam


def titik_dalam_poligon(x, y, poligon):
    """poligon = [cincin_luar, lubang1, lubang2, ...]"""
    if not titik_dalam_cincin(x, y, poligon[0]):
        return False
    return not any(titik_dalam_cincin(x, y, lubang) for lubang in poligon[1:])


def muat_batas(path, properti_nama):
    with open(path, encoding="utf-8") as f:
        gj = json.load(f)
    wilayah = []
    for fitur in gj.get("features", []):
        nama = fitur.get("properties", {}).get(properti_nama)
        geom = fitur.get("geometry", {})
        if geom.get("type") == "Polygon":
            polis = [geom["coordinates"]]
        elif geom.get("type") == "MultiPolygon":
            polis = geom["coordinates"]
        else:
            continue
        wilayah.append((nama, polis))
    if not wilayah:
        sys.exit(f"Tidak ada poligon di {path}. Periksa format GeoJSON-nya.")
    return wilayah


def cari_kecamatan(lat, lng, wilayah):
    for nama, polis in wilayah:
        if any(titik_dalam_poligon(lng, lat, pg) for pg in polis):
            return nama
    return None


def muat_kamus(path):
    tipe, kata = {}, []
    with open(path, encoding="utf-8") as f:
        for r in csv.DictReader(f):
            nilai = (r["sektor"], r["keranjang"])
            if r["jenis"] == "tipe":
                tipe[r["pola"].strip()] = nilai
            else:
                kata.append((r["pola"].strip().lower(), nilai))
    return tipe, kata


def golongkan(baris, kamus_tipe, kamus_kata):
    """Urutan bukti: kata kunci di nama → tipe utama → tipe lain."""
    nama = normalkan_nama(baris["nama"])
    for pola, nilai in kamus_kata:
        if re.search(r"\b" + re.escape(pola) + r"\b", nama):
            return nilai
    if baris.get("tipe_utama") in kamus_tipe:
        return kamus_tipe[baris["tipe_utama"]]
    for t in (baris.get("tipe") or "").split("|"):
        if t in kamus_tipe:
            return kamus_tipe[t]
    return ("tak-dikenal", "abu-abu")


def main():
    p = argparse.ArgumentParser(description="Cleansing hasil panen.")
    p.add_argument("--masukan", required=True)
    p.add_argument("--batas", required=True, help="GeoJSON batas kecamatan")
    p.add_argument("--properti-nama", default="kecamatan")
    p.add_argument("--kamus", default="kamus_kategori.csv")
    p.add_argument("--keluaran", default="bersih.csv")
    a = p.parse_args()

    with open(a.masukan, encoding="utf-8") as f:
        baris = list(csv.DictReader(f))
    corong = [("Panen mentah", len(baris))]

    # 1. duplikat persis
    unik = {}
    for b in baris:
        unik.setdefault(b["place_id"], b)
    baris = list(unik.values())
    corong.append(("Setelah buang duplikat place_id", len(baris)))

    # 2. duplikat dekat — urutkan dulu supaya yang ulasannya terbanyak dipertahankan
    baris.sort(key=lambda b: -int(float(b.get("jumlah_ulasan") or 0)))
    simpan = []
    per_nama = {}
    for b in baris:
        kunci = normalkan_nama(b["nama"])
        lat, lng = float(b["lat"]), float(b["lng"])
        kembar = any(jarak_meter(lat, lng, x, y) < JARAK_DUPLIKAT_M for x, y in per_nama.get(kunci, []))
        if kembar:
            continue
        per_nama.setdefault(kunci, []).append((lat, lng))
        simpan.append(b)
    baris = simpan
    corong.append((f"Setelah buang duplikat dekat (< {JARAK_DUPLIKAT_M} m)", len(baris)))

    # 3. tutup permanen
    baris = [b for b in baris if b.get("status_usaha") != "CLOSED_PERMANENTLY"]
    corong.append(("Setelah buang yang tutup permanen", len(baris)))

    # 4. verifikasi wilayah
    wilayah = muat_batas(a.batas, a.properti_nama)
    dalam = []
    for b in baris:
        kec = cari_kecamatan(float(b["lat"]), float(b["lng"]), wilayah)
        if kec:
            b["kecamatan"] = kec
            dalam.append(b)
    baris = dalam
    corong.append(("Setelah verifikasi wilayah", len(baris)))

    # 5. tiga keranjang
    kamus_tipe, kamus_kata = muat_kamus(a.kamus)
    hitung = Counter()
    hasil = []
    for b in baris:
        sektor, keranjang = golongkan(b, kamus_tipe, kamus_kata)
        hitung[keranjang] += 1
        if keranjang == "luar":
            continue
        b["sektor"], b["keranjang"] = sektor, keranjang
        hasil.append(b)
    corong.append(("Setelah saring kategori (inti + abu-abu)", len(hasil)))

    if not hasil:
        sys.exit("Tidak ada baris tersisa. Periksa batas wilayah dan kamus kategori.")
    kolom = list(hasil[0].keys())
    with open(a.keluaran, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=kolom)
        w.writeheader()
        w.writerows(hasil)

    print("Corong penyaringan")
    for label, n in corong:
        print(f"  {label:<45} {n:>7,}".replace(",", "."))
    print(f"  Keranjang: inti {hitung['inti']}, abu-abu {hitung['abu-abu']}, luar (dibuang) {hitung['luar']}")
    print(f"Disimpan ke {a.keluaran}")


if __name__ == "__main__":
    main()
