#!/usr/bin/env python3
"""Normalize preserved NOAA GHCN-Daily monthly records, entirely offline.

Input is a pinned subset of original fixed-width .dly lines. Temperatures and
precipitation are scaled from tenths; missing -9999 becomes NA. Per-element
measurement, quality and source flags are preserved. No quality filtering,
imputation, station merging or timezone inference takes place here.
"""
import argparse
import calendar
import csv
import datetime
import io
from pathlib import Path

ELEMENTS = ("TMAX", "TMIN", "PRCP")
HEADER = ["observation_id", "station", "date", "day_of_year", "TMAX_DegC", "TMIN_DegC", "PRCP_mm"]
HEADER += [f"{element}_{flag}" for element in ELEMENTS for flag in ("mflag", "qflag", "sflag")]


def magnitude(value: int) -> str:
    if value == -9999:
        return "NA"
    return ("-" if value < 0 else "") + f"{abs(value) // 10}.{abs(value) % 10}"


def convert(source: bytes) -> bytes:
    records = {}
    stations = set()
    years = set()
    for raw in source.decode("ascii").splitlines():
        if len(raw) != 269:
            raise ValueError("Each source month record must have exactly 269 fixed-width characters")
        station, year, month, element = raw[:11], int(raw[11:15]), int(raw[15:17]), raw[17:21]
        if element not in ELEMENTS:
            raise ValueError(f"Unexpected element {element}; retain only TMAX/TMIN/PRCP")
        if not 1 <= month <= 12:
            raise ValueError("Invalid source month")
        key = station, year, month, element
        if key in records:
            raise ValueError("Duplicate station/year/month/element record")
        records[key] = raw
        stations.add(station)
        years.add(year)
    if len(stations) != 1 or len(years) != 1:
        raise ValueError("This example converter requires exactly one station and one complete year")
    station, year = next(iter(stations)), next(iter(years))
    if len(records) != 36:
        raise ValueError("A complete year requires twelve months of all three elements")
    output = io.StringIO(newline="")
    writer = csv.writer(output, lineterminator="\n")
    writer.writerow(HEADER)
    for month in range(1, 13):
        for day in range(1, calendar.monthrange(year, month)[1] + 1):
            date = datetime.date(year, month, day)
            cells = {}
            for element in ELEMENTS:
                record = records[station, year, month, element]
                field = record[21 + (day - 1) * 8:29 + (day - 1) * 8]
                cells[element] = (int(field[:5]),) + tuple("" if c == " " else c for c in field[5:8])
            row = [int(date.strftime("%Y%m%d")), station, date.isoformat(), date.timetuple().tm_yday]
            row += [magnitude(cells[element][0]) for element in ELEMENTS]
            row += [value for element in ELEMENTS for value in cells[element][1:]]
            writer.writerow(row)
    return output.getvalue().encode("ascii")


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("source", type=Path)
    parser.add_argument("output", type=Path)
    parser.add_argument("--check", action="store_true", help="Verify output already matches the exact deterministic conversion")
    args = parser.parse_args()
    converted = convert(args.source.read_bytes())
    if args.check:
        if args.output.read_bytes() != converted:
            raise SystemExit("Normalized CSV differs from preserved NOAA source records")
        print("NOAA normalized CSV exactly matches preserved source records")
    else:
        with args.output.open("xb") as output:
            output.write(converted)
        print(f"Wrote {len(converted)} bytes to {args.output}")


if __name__ == "__main__":
    main()
