#!/usr/bin/env python3
"""
Apply county diurnal temperature range to the app CSV.

This joins only the final app-facing metric into climate-data.csv. Detailed ETL
fields stay in data/noaa/county_diurnal_temperature_range.csv.
"""

from __future__ import annotations

import argparse
import csv
from pathlib import Path

from build_county_locally_extreme_data import OLD_APP_FIPS_TO_CURRENT_FIPS


REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_CLIMATE_DATA = REPO_ROOT / "data" / "climate-data.csv"
DEFAULT_DTR_CSV = REPO_ROOT / "data" / "noaa" / "county_diurnal_temperature_range.csv"
METRIC_FIELD = "avgDiurnalTempRangeF"
SOURCE_TAG_PREFIX = "diurnalTempRange="
SOURCE_TAG = "diurnalTempRange=noaa-nclimgrid-daily-1991-2020"
MISSING_SOURCE_TAG = "missing-noaa-diurnal-temperature-range"


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 load_dtr_lookup(path: Path) -> dict[str, dict]:
    """Load DTR rows keyed by current county FIPS plus supported old app FIPS."""
    fieldnames, rows = read_csv_rows(path)
    required_fields = {"countyFips", METRIC_FIELD}
    missing_fields = required_fields - set(fieldnames)
    if missing_fields:
        raise ValueError(f"{path} is missing required fields: {sorted(missing_fields)}")

    current_lookup: dict[str, dict] = {}
    for row in rows:
        county_fips = normalize_fips(row.get("countyFips", ""))
        metric_value = (row.get(METRIC_FIELD) or "").strip()
        if not county_fips or not metric_value:
            continue
        float(metric_value)
        current_lookup[county_fips] = row

    lookup = dict(current_lookup)
    for old_fips, (current_fips, reason) in OLD_APP_FIPS_TO_CURRENT_FIPS.items():
        current_row = current_lookup.get(normalize_fips(current_fips))
        if not current_row:
            continue
        proxy_row = dict(current_row)
        proxy_row["fipsAdjustment"] = reason
        lookup[normalize_fips(old_fips)] = proxy_row

    return lookup


def fieldnames_with_metric(original_fields: list[str]) -> list[str]:
    """Insert the DTR metric near the other temperature field."""
    fields = [field for field in original_fields if field != METRIC_FIELD]
    if "avgTempF" in fields:
        insert_at = fields.index("avgTempF") + 1
    else:
        insert_at = len(fields)
    fields.insert(insert_at, METRIC_FIELD)
    return fields


def replace_dtr_source_tag(source: str, has_value: bool) -> str:
    """Replace previous DTR provenance with the active one."""
    parts = [
        part.strip()
        for part in (source.strip() or "unknown-source").split("+")
        if part.strip()
    ]
    kept_parts = [
        part
        for part in parts
        if not part.startswith(SOURCE_TAG_PREFIX) and part != MISSING_SOURCE_TAG
    ]
    kept_parts.append(SOURCE_TAG if has_value else MISSING_SOURCE_TAG)
    return " + ".join(kept_parts)


def apply_diurnal_temperature_range(
    *,
    climate_data: Path,
    dtr_csv: Path,
    out: Path,
) -> None:
    """Join DTR values into climate-data.csv."""
    original_fields, climate_rows = read_csv_rows(climate_data)
    dtr_lookup = load_dtr_lookup(dtr_csv)
    fieldnames = fieldnames_with_metric(original_fields)

    updated_count = 0
    missing_count = 0
    adjusted_count = 0
    for row in climate_rows:
        county_fips = normalize_fips(row.get("countyFips", ""))
        dtr_row = dtr_lookup.get(county_fips)
        if dtr_row:
            row[METRIC_FIELD] = dtr_row.get(METRIC_FIELD, "")
            row["source"] = replace_dtr_source_tag(row.get("source", ""), has_value=True)
            updated_count += 1
            if dtr_row.get("fipsAdjustment"):
                adjusted_count += 1
        else:
            row[METRIC_FIELD] = ""
            row["source"] = replace_dtr_source_tag(row.get("source", ""), has_value=False)
            missing_count += 1

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

    print(f"Wrote {len(climate_rows)} rows to {out}")
    print(f"Updated {METRIC_FIELD} for {updated_count} rows.")
    print(f"Used supported county-vintage FIPS adjustments for {adjusted_count} rows.")
    print(f"Left {METRIC_FIELD} blank for {missing_count} rows without DTR data.")


def parse_args() -> argparse.Namespace:
    """Parse command-line paths."""
    parser = argparse.ArgumentParser(
        description="Apply average diurnal temperature range to climate-data.csv."
    )
    parser.add_argument("--climate-data", type=Path, default=DEFAULT_CLIMATE_DATA)
    parser.add_argument("--dtr-csv", type=Path, default=DEFAULT_DTR_CSV)
    parser.add_argument("--out", type=Path, default=DEFAULT_CLIMATE_DATA)
    return parser.parse_args()


def main() -> None:
    """Run the climate-data update."""
    args = parse_args()
    apply_diurnal_temperature_range(
        climate_data=args.climate_data,
        dtr_csv=args.dtr_csv,
        out=args.out,
    )


if __name__ == "__main__":
    main()
