#!/usr/bin/env python3
"""
Site Grid — Faz 1: ERA5-Land indirme (CDS).

İki ürün iner (gerekçe: config.py başlığı):
  A) reanalysis-era5-land-monthly-means      -> tavg[12], prec[12]   (küçük)
  B) derived-era5-land-daily-statistics      -> tmin[12], absMin     (asıl hacim)
     daily_statistic = daily_minimum, yalnız 2m_temperature

KULLANIM
    python3 01_download_era5.py                     # PİLOT (İç Anadolu)
    python3 01_download_era5.py --estimate          # indirmeden hacim tahmini
    python3 01_download_era5.py --full --onayliyorum  # TÜM TÜRKİYE (onay şart)

GÜVENLİK KİLİTLERİ
  - CDS anahtarı yoksa hiç başlamaz.
  - Türkiye'nin tamamı için AÇIK onay bayrağı ister.
  - Var olan dosyanın üstüne yazmaz (--force ile geçilir).
"""

from __future__ import annotations

import argparse
import sys
import time
from pathlib import Path

from config import (
    DS_DAILY,
    DS_MONTHLY,
    GRID_STEP,
    NORMAL_END_YEAR,
    NORMAL_START_YEAR,
    PILOT_BBOX,
    RAW_DIR,
    TURKEY_BBOX,
    BBox,
    cds_key_present,
)

MONTHS = [f"{m:02d}" for m in range(1, 13)]
DAYS = [f"{d:02d}" for d in range(1, 32)]


def years() -> list[str]:
    return [str(y) for y in range(NORMAL_START_YEAR, NORMAL_END_YEAR + 1)]


def estimate_mb(bbox: BBox) -> dict[str, float]:
    """Sıkıştırılmamış float32 üzerinden kaba hacim tahmini."""
    cells = bbox.cell_count(GRID_STEP)
    n_years = NORMAL_END_YEAR - NORMAL_START_YEAR + 1
    n_days = int(n_years * 365.25)
    n_months = n_years * 12
    mb = lambda n: n * cells * 4 / 1024 / 1024  # noqa: E731
    return {
        "cells": cells,
        "monthly_2var_mb": mb(n_months) * 2,
        "daily_min_mb": mb(n_days),
        "toplam_mb": mb(n_months) * 2 + mb(n_days),
        # Karşılaştırma: aynı bilgiyi SAATLİK üründen almak.
        "saatlik_olsaydi_mb": mb(n_days * 24),
    }


def download_monthly(client, bbox: BBox, out: Path) -> None:
    """Aylık ortalamalar: 2m sıcaklık + toplam yağış."""
    client.retrieve(
        DS_MONTHLY,
        {
            "product_type": ["monthly_averaged_reanalysis"],
            "variable": ["2m_temperature", "total_precipitation"],
            "year": years(),
            "month": MONTHS,
            "time": ["00:00"],
            "data_format": "netcdf",
            "download_format": "unarchived",
            "area": bbox.as_cds_area(),
        },
    ).download(str(out))


def download_daily_min(client, bbox: BBox, out_dir: Path, force: bool) -> list[Path]:
    """
    Günlük minimum sıcaklık — TÜM YILLARI ÖNCE KUYRUĞA VER, sonra biteni indir.

    NEDEN BÖYLE (ölçüldü 2026-09-04/05):
      Sıralı `retrieve()` her yıl için AYRI kuyruk beklemesi yapıyor ve
      `derived-era5-land-daily-statistics` kuyruğu çok yavaş:
        1991 -> 89 dk kuyruk + 18 dk işlem = 107 dk
        yetim iş -> 120+ dk kuyrukta
      30 yıl sıralı = ~50 saat. Oysa kuyruk süresi İSTEK BOYUTUNA değil,
      türetilmiş veri seti işçi havuzunun meşguliyetine bağlı. Bütün yılları
      baştan kuyruğa verince 30 bekleme ARDIŞIK değil PARALEL yaşanıyor.

    NOT: istek başına en fazla 1 YIL alınabiliyor (ölçüldü: 2 ve 3 yıl
    `403 cost limits exceeded`). Alan kısıt değil: Türkiye tam bbox + 1 yıl
    kabul ediliyor. Yani tam koşu da 30 istektir.
    """
    todo: list[str] = []
    written: list[Path] = []
    for year in years():
        out = out_dir / f"daily_tmin_{bbox.name}_{year}.nc"
        if out.exists() and not force:
            written.append(out)
        else:
            todo.append(year)

    if written:
        print(f"  {len(written)} yıl zaten var, atlanıyor")
    if not todo:
        return written

    # --- 1) hepsini kuyruğa ver -------------------------------------------
    print(f"  {len(todo)} yıl kuyruğa veriliyor...")
    jobs: dict[str, object] = {}
    for year in todo:
        try:
            jobs[year] = client.client.submit(
                DS_DAILY,
                {
                    "variable": ["2m_temperature"],
                    "year": year,
                    "month": MONTHS,
                    "day": DAYS,
                    "daily_statistic": "daily_minimum",
                    "time_zone": "utc+00:00",
                    "frequency": "1_hourly",
                    "area": bbox.as_cds_area(),
                },
            )
        except Exception as exc:  # noqa: BLE001
            print(f"    {year}: gönderilemedi -> {exc}")
    print(f"  {len(jobs)} iş kuyrukta. Bitenler indiriliyor...")

    # --- 2) bitenleri topla -----------------------------------------------
    t0 = time.time()
    pending = dict(jobs)
    while pending:
        done_now: list[str] = []
        for year, job in list(pending.items()):
            try:
                status = job.status  # type: ignore[attr-defined]
            except Exception as exc:  # noqa: BLE001
                print(f"    {year}: durum okunamadı -> {exc}")
                continue
            if status == "successful":
                out = out_dir / f"daily_tmin_{bbox.name}_{year}.nc"
                try:
                    job.download(str(out))  # type: ignore[attr-defined]
                    mb = out.stat().st_size / 1024 / 1024
                    print(f"    {year}: {mb:5.2f} MB  (toplam {time.time() - t0:.0f} sn)")
                    written.append(out)
                except Exception as exc:  # noqa: BLE001
                    print(f"    {year}: indirilemedi -> {exc}")
                done_now.append(year)
            elif status == "failed":
                print(f"    {year}: BAŞARISIZ -> {getattr(job, 'error', '?')}")
                done_now.append(year)
        for y in done_now:
            pending.pop(y, None)
        if pending:
            print(f"    bekleyen {len(pending)} · geçen {time.time() - t0:.0f} sn", end="\r")
            time.sleep(30)
    print()
    return written


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--full", action="store_true", help="Türkiye'nin tamamı")
    ap.add_argument("--onayliyorum", action="store_true", help="--full için açık onay")
    ap.add_argument("--estimate", action="store_true", help="yalnız hacim tahmini")
    ap.add_argument("--force", action="store_true", help="var olan dosyaların üstüne yaz")
    args = ap.parse_args()

    bbox = TURKEY_BBOX if args.full else PILOT_BBOX
    est = estimate_mb(bbox)

    print(f"Kapsam      : {bbox.name}  [K{bbox.north} B{bbox.west} G{bbox.south} D{bbox.east}]")
    print(f"Izgara      : {est['cells']:,} hücre @ {GRID_STEP}°")
    print(f"Dönem       : {NORMAL_START_YEAR}-{NORMAL_END_YEAR}")
    print(f"Aylık (2 değişken) : ~{est['monthly_2var_mb']:.1f} MB")
    print(f"Günlük minimum     : ~{est['daily_min_mb']:.1f} MB")
    print(f"TOPLAM (tahmini)   : ~{est['toplam_mb']:.1f} MB")
    print(f"Saatlik olsaydı    : ~{est['saatlik_olsaydi_mb']:.0f} MB "
          f"({est['saatlik_olsaydi_mb'] / max(est['toplam_mb'], 1):.0f}x)")

    if args.estimate:
        return 0

    if args.full and not args.onayliyorum:
        print("\nHATA: Türkiye'nin tamamı için --onayliyorum bayrağı şart.", file=sys.stderr)
        return 2

    if not cds_key_present():
        print(
            "\nHATA: CDS anahtarı bulunamadı.\n"
            "  ~/.cdsapirc dosyası ya da CDSAPI_URL + CDSAPI_KEY ortam değişkenleri gerekli.\n"
            "  Anahtar alma: https://cds.climate.copernicus.eu/ (hesap + Terms kabulü)",
            file=sys.stderr,
        )
        return 3

    try:
        import cdsapi  # noqa: PLC0415
    except ImportError:
        print("\nHATA: cdsapi kurulu değil. `pip install -r requirements.txt`", file=sys.stderr)
        return 4

    RAW_DIR.mkdir(parents=True, exist_ok=True)
    client = cdsapi.Client()

    monthly_out = RAW_DIR / f"monthly_{bbox.name}.nc"
    if monthly_out.exists() and not args.force:
        print(f"\nAylık: zaten var ({monthly_out.name})")
    else:
        print("\nAylık ortalamalar indiriliyor...")
        t0 = time.time()
        download_monthly(client, bbox, monthly_out)
        print(f"  bitti: {monthly_out.stat().st_size / 1024 / 1024:.1f} MB · {time.time() - t0:.0f} sn")

    print("\nGünlük minimum sıcaklık indiriliyor (yıl yıl)...")
    t0 = time.time()
    files = download_daily_min(client, bbox, RAW_DIR, args.force)
    total_mb = sum(f.stat().st_size for f in files) / 1024 / 1024
    print(f"\nToplam günlük: {total_mb:.1f} MB · {time.time() - t0:.0f} sn")
    print(f"Ham veri: {RAW_DIR}")
    return 0


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