""".. title :: PRISM Data by Point

Return to `API Services </api/#json>`_

Documentation for /json/prism.py
--------------------------------

This service emits a JSON response of PRISM data for a given point.

Changelog
---------

- 2024-07-24: Initial documentation release and rectify the dates.

Example Usage
-------------

Request PRISM data for January 2024 at a point in Iowa:

https://mesonet.agron.iastate.edu/json/prism.py?lat=41.9&lon=-92.0&\
sdate=2024-01-01&edate=2024-01-31

Request PRISM data for January 10th 2024 at a point in Iowa:

https://mesonet.agron.iastate.edu/json/prism.py?lat=41.9&lon=-92.0&\
valid=2024-01-10

"""

import json
import os
from datetime import date
from typing import Annotated

import numpy as np
import pandas as pd
from pydantic import Field, model_validator
from pyiem.exceptions import IncompleteWebRequest
from pyiem.grid.nav import get_nav
from pyiem.iemre import daily_offset
from pyiem.util import c2f, mm2inch, ncopen
from pyiem.webutil import CGIModel, iemapp

from iemweb.fields import CALLBACK_FIELD, LATITUDE_FIELD, LONGITUDE_FIELD
from iemweb.util import json_response_dict


class Schema(CGIModel):
    """See how we are called."""

    callback: CALLBACK_FIELD = None
    lat: LATITUDE_FIELD = 41.9
    lon: LONGITUDE_FIELD = -92.0
    valid: Annotated[
        date | None,
        Field(
            description="Provide data valid for this date (~12 UTC)",
        ),
    ] = None
    sdate: Annotated[
        date | None,
        Field(
            description="Inclusive start date for data request",
        ),
    ] = None
    edate: Annotated[
        date | None,
        Field(
            description="Inclusive end date for data request",
        ),
    ] = None

    @model_validator(mode="after")
    def validate_dates(self):
        """Ensure we have valid dates."""
        if (
            self.sdate is not None
            and self.edate is not None
            and self.sdate > self.edate
        ):
            raise ValueError("sdate must be before edate")
        return self


def myrounder(val, precision):
    """round a float or give back None"""
    if val is None or np.isnan(val) or np.ma.is_masked(val):
        return None
    return round(val, precision)


def dowork(environ: dict):
    """Do work!"""
    dates = []
    if environ["valid"] is not None:
        dates.append(environ["valid"])
    elif environ["sdate"] is not None and environ["edate"] is not None:
        dates = pd.date_range(environ["sdate"], environ["edate"]).tolist()
    else:
        raise IncompleteWebRequest("Need valid or sdate/edate")

    i, j = get_nav("prism").find_ij(environ["lon"], environ["lat"])
    if i is None or j is None:
        raise IncompleteWebRequest("Coordinates outside of domain")

    res = json_response_dict(
        {
            "gridi": int(i),
            "gridj": int(j),
            "data": [],
            "disclaimer": (
                "PRISM Climate Group, Oregon State University, "
                "https://prism.oregonstate.edu, created 4 Feb 2004."
            ),
        }
    )

    sidx = daily_offset(dates[0])
    eidx = daily_offset(dates[-1]) + 1

    ncfn = f"/mesonet/data/prism/{dates[0]:%Y}_daily.nc"
    if os.path.isfile(ncfn):
        with ncopen(ncfn) as nc:
            tmax = nc.variables["tmax"][sidx:eidx, j, i]
            tmin = nc.variables["tmin"][sidx:eidx, j, i]
            ppt = nc.variables["ppt"][sidx:eidx, j, i]

        for mt, nt, pt, valid in zip(tmax, tmin, ppt, dates, strict=False):
            res["data"].append(
                {
                    "valid": valid.strftime("%Y-%m-%dT12:00:00Z"),
                    "high_f": myrounder(c2f(mt), 1),
                    "low_f": myrounder(c2f(nt), 1),
                    "precip_in": myrounder(mm2inch(pt), 2),
                }
            )

    return json.dumps(res)


def get_mckey(environ):
    """get key."""
    return (
        f"/json/prism/{environ['lon']:.2f}/"
        f"{environ['lat']:.2f}/{environ['valid']}/{environ['sdate']}/"
        f"{environ['edate']}"
    )


@iemapp(
    help=__doc__,
    schema=Schema,
    memcachekey=get_mckey,
)
def application(environ, start_response):
    """Answer request."""
    res = dowork(environ)
    start_response("200 OK", [("Content-type", "application/json")])
    return res
