""".. title:: NWS TOR+SVR Warning Time-Mot-Loc

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

Changelog
---------

- 2026-03-20: Ill-defined `all` parameter needs to be set to something
  truthy and not just present.
- 2024-08-25: Initial documentation and pydantic validation

Example Requests
----------------

Provide values for a time on 21 May 2024

https://mesonet.agron.iastate.edu/request/grx/time_mot_loc.txt\
?all=1&version=1.5&valid=2024-05-21T20:00:00Z

"""

import math
import re
from datetime import timedelta
from io import StringIO
from typing import Annotated
from zoneinfo import ZoneInfo

from pydantic import AwareDatetime, Field
from pyiem.database import get_dbconnc
from pyiem.util import utc
from pyiem.webutil import CGIModel, iemapp

LOLA = re.compile(r"(?P<lon>[0-9\.\-]+) (?P<lat>[0-9\.\-]+)")
SQLISO = "YYYY-MM-DDThh24:MI:SSZ"


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

    all: Annotated[
        bool | None,
        Field(
            description="Include all warnings, not just Tornado",
        ),
    ] = False
    valid: Annotated[
        AwareDatetime | None,
        Field(
            description="The valid time to generate the product for.",
        ),
    ] = None
    version: Annotated[
        float,
        Field(
            description="The version of the GR product to generate.",
        ),
    ] = 1.0


def extrapolate(lon, lat, distance, drct):
    """Fun."""
    lon2 = lon + (
        (distance * math.cos(math.radians(270.0 - drct)))
        / (111325.0 * math.cos(math.radians(lat)))
    )
    lat2 = lat + ((distance * math.sin(math.radians(270.0 - drct))) / 111325.0)
    return lon2, lat2


def gentext(sio, row, grversion):
    """Turn the database row into placefile worthy text."""
    smps = row["tml_sknt"] * 0.5144
    drct = row["tml_direction"]
    tml_valid = row["tml_valid"].astimezone(ZoneInfo("UTC"))
    duration = (row["polygon_end"] - row["tml_valid"]).total_seconds()
    distance = smps * duration
    lats = []
    lons = []
    time_range = ""
    if grversion >= 1.5:
        time_range = f"TimeRange: {row['iso_begin']} {row['iso_end']}\n"
    if row["lat"] is not None:
        lons.append(row["lon"])
        lats.append(row["lat"])
        # Point events are represented by lines
        lon2, lat2 = extrapolate(row["lon"], row["lat"], distance, drct)
        sio.write(
            f"Color: 255 255 0\n{time_range}"
            f'Line: 2,0,"NWS Warning Track ({row["wfo"]}-{row["phenomena"]}'
            f"-{row['eventid']})\\n"
            f'{drct:.0f}/{row["tml_sknt"]:.0f}"\n'
            f"{row['lat']:.4f},{row['lon']:.4f}\n"
            f"{lat2:.4f},{lon2:.4f}\n"
            "End:\n"
        )
    elif row["line"] is not None:
        # Lines are shown as three sets of lines.
        for token in LOLA.findall(row["line"]):
            lons.append(float(token[0]))
            lats.append(float(token[1]))
        for seconds in [0, duration / 2.0, duration]:
            valid = tml_valid + timedelta(seconds=seconds)
            ts = valid.strftime("%H%Mz")
            sio.write(
                f"Color: 255 255 0\n{time_range}"
                f'Line: 2,0,"NWS Warning Track @{ts}'
                f"({row['wfo']}-{row['phenomena']}"
                f"-{row['eventid']})\\n"
                f'{drct:.0f}/{row["tml_sknt"]:.0f}"\n'
            )
            for lon, lat in zip(lons, lats, strict=False):
                lon2, lat2 = extrapolate(lon, lat, seconds * smps, drct)
                sio.write(f"{lat2:.4f},{lon2:.4f}\n")
            sio.write("End:\n")

    # Now we place places along the track.
    sio.write("Color: 255 255 255\nThreshold:10\n\n")
    for lon, lat in zip(lons, lats, strict=False):
        for minute in range(int(duration / 60.0) + 1):
            valid = tml_valid + timedelta(minutes=minute)
            ts = valid.strftime("%H%Mz")
            lon2, lat2 = extrapolate(lon, lat, smps * minute * 60, drct)
            sio.write(f"Place: {lat2:.4f},{lon2:.4f},{ts}\n")
    sio.write("Threshold: 999\n")


@iemapp(help=__doc__, schema=Schema)
def application(environ: dict, start_response: callable):
    """Our WSGI service."""
    grversion = environ["version"]
    phenoms = ["TO", "SV"] if environ["all"] is not None else ["TO"]
    pgconn, cursor = get_dbconnc("postgis")
    valid = utc()
    refresh = 60
    if environ["valid"]:
        valid = environ["valid"]
        refresh = 86400
    t1 = valid
    t2 = valid
    tmlabel = valid.strftime("%H%Mz")
    if grversion >= 1.5 or environ["valid"]:
        # Pull larger window of data to support TimeRange
        t1 = valid - timedelta(hours=2)
        t2 = valid + timedelta(hours=2)
        tmlabel = valid.strftime("%b %d %Y %H%Mz")
    cursor.execute(
        f"""SELECT ST_x(tml_geom) as lon, ST_y(tml_geom) as lat,
        ST_AsText(tml_geom_line) as line,
        tml_valid, tml_direction, tml_sknt,
        polygon_begin, polygon_end, eventid, wfo, phenomena,
        to_char(polygon_begin at time zone 'UTC', '{SQLISO}') as iso_begin,
        to_char(polygon_end at time zone 'UTC', '{SQLISO}') as iso_end
        from sbw WHERE vtec_year = %s and polygon_end > %s and
        polygon_begin < %s and phenomena = ANY(%s)
        and tml_direction is not null and status != 'CAN' and
        polygon_end > polygon_begin""",
        (valid.year, t1, t2, phenoms),
    )
    sio = StringIO()
    label = "TOR+SVR" if environ["all"] is not None else "TOR"
    sio.write(
        f"RefreshSeconds: {refresh}\n"
        "Threshold: 999\n"
        f"Title: NWS {label} @{tmlabel} Warning Time-Mot-Loc\n"
        'Font: 1, 11, 1, "Courier New"\n\n'
    )
    for row in cursor:
        gentext(sio, row, grversion)
    pgconn.close()
    start_response("200 OK", [("Content-type", "text/plain")])
    return [sio.getvalue().encode("utf-8")]
