#!/usr/bin/env python3
"""
Site Grid — toprak ızgarasını Supabase'e yükle (`agri_soil_grid`).

Neden PostgREST toplu insert: bu ortamda doğrudan Postgres bağlantı dizesi yok;
service role anahtarı ile REST üzerinden yığın halinde yazmak bu oturumda
kanıtlanmış yol (bkz. 06 öncesi `seed-source-cache.mjs`).

Kullanım:
    python3 06_load_soil_grid.py            # pilot
    python3 06_load_soil_grid.py --full     # Türkiye
    python3 06_load_soil_grid.py --full --truncate   # önce tabloyu boşalt

Kimlik bilgisi .env.local'dan okunur, LOGLANMAZ.
"""

from __future__ import annotations

import argparse
import json
import math
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

from config import COPERNICUS_ATTRIBUTION, OUT_DIR, PILOT_BBOX, TURKEY_BBOX, BBox

try:
    import pandas as pd
except ImportError:
    print("HATA: pandas gerekli.", file=sys.stderr)
    raise SystemExit(4) from None

ROOT = Path(__file__).resolve().parents[2]
BATCH = 5000


def load_env() -> tuple[str, str]:
    env: dict[str, str] = {}
    for name in (".env.local", ".env"):
        p = ROOT / name
        if not p.exists():
            continue
        for line in p.read_text().splitlines():
            if "=" in line and not line.strip().startswith("#"):
                k, v = line.split("=", 1)
                env.setdefault(k.strip(), v.strip().strip("\"'"))
    url = env.get("NEXT_PUBLIC_SUPABASE_URL") or env.get("SUPABASE_URL")
    key = env.get("SUPABASE_SERVICE_ROLE_KEY")
    if not url or not key:
        print("HATA: SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY bulunamadı.", file=sys.stderr)
        raise SystemExit(3)
    return url, key


def sanitize(row: dict) -> dict:
    """
    NaN/Inf -> None. JSON'da `NaN` GEÇERSİZDİR; PostgREST `PGRST102 Empty or
    invalid json` ile reddediyor (ölçüldü: 40.000. satırda patladı). pandas
    float sütunlarında eksik değer NaN olarak kaldığı için burada tek noktadan
    temizleniyor.
    """
    out: dict = {}
    for k, v in row.items():
        if isinstance(v, float) and not math.isfinite(v):
            out[k] = None
        elif hasattr(v, "item"):  # numpy skaları -> python
            out[k] = v.item()
        else:
            out[k] = v
    return out


def post(url: str, key: str, path: str, payload: list[dict], prefer: str) -> None:
    req = urllib.request.Request(
        f"{url}/rest/v1/{path}",
        data=json.dumps(payload).encode(),
        headers={
            "apikey": key,
            "authorization": f"Bearer {key}",
            "content-type": "application/json",
            "prefer": prefer,
        },
        method="POST",
    )
    try:
        urllib.request.urlopen(req, timeout=180).read()  # noqa: S310
    except urllib.error.HTTPError as e:
        body = e.read().decode()[:400]
        raise SystemExit(f"HATA {e.code}: {body}") from e


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--full", action="store_true")
    ap.add_argument("--truncate", action="store_true", help="yüklemeden önce tabloyu boşalt")
    args = ap.parse_args()

    bbox: BBox = TURKEY_BBOX if args.full else PILOT_BBOX
    csv = OUT_DIR / f"soil_grid_{bbox.name}.csv"
    if not csv.exists():
        print(f"HATA: {csv} yok. Önce 04_build_soil_grid.py çalıştır.", file=sys.stderr)
        return 3

    url, key = load_env()
    df = pd.read_csv(csv)
    print(f"Kaynak: {csv.name}  {len(df):,} satır")

    if args.truncate:
        req = urllib.request.Request(
            f"{url}/rest/v1/agri_soil_grid?lat_e2=gte.-32768",
            headers={"apikey": key, "authorization": f"Bearer {key}", "prefer": "return=minimal"},
            method="DELETE",
        )
        urllib.request.urlopen(req, timeout=600).read()  # noqa: S310
        print("Tablo boşaltıldı.")

    # lat/lng -> smallint ızgara anahtarı
    df["lat_e2"] = (df["lat"] * 100).round().astype(int)
    df["lng_e2"] = (df["lng"] * 100).round().astype(int)
    cols = ["lat_e2", "lng_e2", "ph", "sand", "silt", "clay", "som_pct"]
    df = df[cols]

    n = len(df)
    batches = math.ceil(n / BATCH)
    t0 = time.time()
    for i in range(batches):
        chunk = [sanitize(r) for r in df.iloc[i * BATCH : (i + 1) * BATCH].to_dict("records")]
        post(url, key, "agri_soil_grid?on_conflict=lat_e2,lng_e2", chunk,
             "resolution=merge-duplicates,return=minimal")
        done = min((i + 1) * BATCH, n)
        rate = done / max(time.time() - t0, 0.001)
        print(f"  {done:,}/{n:,}  ({100 * done / n:5.1f}%)  {rate:,.0f} satır/sn", end="\r")
    print(f"\nYüklendi: {n:,} satır · {time.time() - t0:.0f} sn")

    # Künye
    post(
        url, key, "agri_grid_meta?on_conflict=layer",
        [
            {
                "layer": "soil",
                "source": "ISRIC SoilGrids 2.0",
                "dataset": "phh2o/sand/silt/clay/soc · 0-5, 5-15, 15-30 cm · WCS 2.0.1",
                "resolution": "0,01° (~1,1 km); kaynak 250 m, kalınlık ağırlıklı 0-30 cm",
                "period": None,
                "bbox": f"K{bbox.north} B{bbox.west} G{bbox.south} D{bbox.east}",
                "row_count": n,
                "attribution": "SoilGrids 2.0, ISRIC — CC BY 4.0",
                "notes": (
                    "Doku sınıfı TS tarafında (usdaTextureClass/faoTextureCategory). "
                    "0,01° indirgeme sadakati ölçüldü: pH MAE 0,049 bias +0,001. "
                    f"Copernicus notu: {COPERNICUS_ATTRIBUTION}"
                ),
            }
        ],
        "resolution=merge-duplicates,return=minimal",
    )
    print("Künye yazıldı (agri_grid_meta).")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
