#!/usr/bin/env python3
"""
Add wettest/driest precipitation month metrics to data/climate-data.csv.

The metrics use the existing monthly NOAA nClimGrid precipitation file. They
are categorical month labels derived from 1991-2020 county-area means.
"""

from __future__ import annotations

import argparse
import csv
from pathlib import Path

import numpy as np
import xarray as xr

from build_county_climate_data import (
    MONTH_NAMES,
    _as_monthly_climatology,
    _extract_grid_2d,
    _load_counties,
    _select_data_var,
    _zonal_mean,
)


REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_CLIMATE_DATA = REPO_ROOT / "data" / "climate-data.csv"
DEFAULT_COUNTIES_GEOJSON = REPO_ROOT / "data" / "geojson-counties-fips.json"
DEFAULT_MONTHLY_PRCP_NC = REPO_ROOT / "data" / "noaa" / "nclimgrid" / "nclimgrid_prcp.nc"
PRECIP_MONTH_FIELDS = ["wettestPrecipMonth", "driestPrecipMonth"]


def normalize_fips(value: object) -> str:
    """Return a 5-digit county FIPS string from mixed text or numeric input."""
    digits = "".join(ch for ch in str(value).strip() if ch.isdigit())
    return digits.zfill(5)[-5:] if digits else ""


def read_csv_rows(path: Path) -> tuple[list[str], list[dict]]:
    """Read a CSV while preserving the source field order."""
    with path.open("r", encoding="utf-8-sig", newline="") as csv_file:
        reader = csv.DictReader(csv_file)
        if reader.fieldnames is None:
            raise ValueError(f"{path} has no CSV header.")
        return list(reader.fieldnames), list(reader)


def fieldnames_with_precip_months(original_fields: list[str]) -> list[str]:
    """Place the new fields next to the existing precipitation metrics."""
    fields = [field for field in original_fields if field not in PRECIP_MONTH_FIELDS]
    insert_after = "seasonalityIndex"
    insert_at = fields.index(insert_after) + 1 if insert_after in fields else len(fields)
    return fields[:insert_at] + PRECIP_MONTH_FIELDS + fields[insert_at:]


def build_precip_month_lookup(
    *,
    counties_geojson: Path,
    monthly_prcp_nc: Path,
    climatology_start_year: int,
    climatology_end_year: int,
) -> dict[str, tuple[str, str]]:
    """Calculate wettest and driest precipitation month for each county."""
    counties = _load_counties(counties_geojson)
    monthly_prcp = xr.open_dataset(monthly_prcp_nc, decode_times=True)
    try:
        prcp_var = _select_data_var(monthly_prcp, "mlyprcp_norm")
        prcp_monthly = _as_monthly_climatology(
            monthly_prcp[prcp_var],
            start_year=climatology_start_year,
            end_year=climatology_end_year,
        )

        prcp_by_month: list[list[float]] = []
        for month in range(12):
            prcp_arr, prcp_transform = _extract_grid_2d(prcp_monthly.isel(time=month))
            prcp_by_month.append(_zonal_mean(prcp_arr, prcp_transform, counties))
    finally:
        monthly_prcp.close()

    lookup: dict[str, tuple[str, str]] = {}
    for idx, row in counties.iterrows():
        county_fips = str(row["county_fips"])
        prcp_months_mm = np.array([prcp_by_month[m][idx] for m in range(12)], dtype=np.float64)
        if not np.any(np.isfinite(prcp_months_mm)):
            continue

        wettest_month = MONTH_NAMES[int(np.nanargmax(prcp_months_mm))]
        driest_month = MONTH_NAMES[int(np.nanargmin(prcp_months_mm))]
        lookup[county_fips] = (wettest_month, driest_month)

    return lookup


def apply_precipitation_month_metrics(
    *,
    climate_data: Path,
    counties_geojson: Path,
    monthly_prcp_nc: Path,
    out: Path,
    climatology_start_year: int,
    climatology_end_year: int,
) -> None:
    """Merge precipitation month categories into the app climate CSV."""
    original_fields, rows = read_csv_rows(climate_data)
    lookup = build_precip_month_lookup(
        counties_geojson=counties_geojson,
        monthly_prcp_nc=monthly_prcp_nc,
        climatology_start_year=climatology_start_year,
        climatology_end_year=climatology_end_year,
    )

    updated_count = 0
    missing_count = 0
    for row in rows:
        county_fips = normalize_fips(row.get("countyFips", ""))
        values = lookup.get(county_fips)
        if values:
            row["wettestPrecipMonth"], row["driestPrecipMonth"] = values
            updated_count += 1
        else:
            row["wettestPrecipMonth"] = ""
            row["driestPrecipMonth"] = ""
            missing_count += 1

        source = (row.get("source") or "").strip() or "unknown-source"
        tag = f"precip-month-extremes=noaa-nclimgrid-monthly-{climatology_start_year}-{climatology_end_year}"
        if tag not in source:
            row["source"] = f"{source} + {tag}"

    fieldnames = fieldnames_with_precip_months(original_fields)
    with out.open("w", encoding="utf-8", newline="") as csv_file:
        writer = csv.DictWriter(csv_file, fieldnames=fieldnames, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(rows)

    print(f"Wrote {len(rows)} rows to {out}")
    print(f"Updated precipitation month metrics for {updated_count} rows.")
    print(f"Left precipitation month metrics blank for {missing_count} rows.")


def parse_args() -> argparse.Namespace:
    """Parse command-line paths for the precipitation month update."""
    parser = argparse.ArgumentParser(
        description="Add wettestPrecipMonth and driestPrecipMonth to climate-data.csv."
    )
    parser.add_argument("--climate-data", type=Path, default=DEFAULT_CLIMATE_DATA)
    parser.add_argument("--counties-geojson", type=Path, default=DEFAULT_COUNTIES_GEOJSON)
    parser.add_argument("--monthly-prcp-nc", type=Path, default=DEFAULT_MONTHLY_PRCP_NC)
    parser.add_argument("--out", type=Path, default=DEFAULT_CLIMATE_DATA)
    parser.add_argument("--climatology-start-year", type=int, default=1991)
    parser.add_argument("--climatology-end-year", type=int, default=2020)
    return parser.parse_args()


def main() -> None:
    """Run the CSV update."""
    args = parse_args()
    apply_precipitation_month_metrics(
        climate_data=args.climate_data,
        counties_geojson=args.counties_geojson,
        monthly_prcp_nc=args.monthly_prcp_nc,
        out=args.out,
        climatology_start_year=args.climatology_start_year,
        climatology_end_year=args.climatology_end_year,
    )


if __name__ == "__main__":
    main()
