""".. title:: GeoJSON of stations within a network

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

Documentation for /geojson/network.py
-------------------------------------

This service provides a GeoJSON representation of stations within a given
network.

Changelog
---------

- 2026-02-24: For IEM consistency, the `generation_time` root attribute was
  renamed `generated_at`.
- 2026-01-26: Added `has_attribute` parameter to allow a filtered result to
  those stations with that attribute set.
- 2024-11-25: Added `network` overload value of `HAS_HML` for sites with HML
  data.
- 2024-08-19: Initial documentation update

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

Provide Iowa DCP sites with hourly precipitation available.

https://mesonet.agron.iastate.edu/geojson/network.py?\
network=IA_DCP&has_attribute=HAS_PHOUR

Provide all sites with 1 minute ASOS data:

https://mesonet.agron.iastate.edu/geojson/network.py?network=ASOS1MIN

Provide all sites with TAF data:

https://mesonet.agron.iastate.edu/geojson/network.py?network=TAF

Provide sites that are online within ASOS class networks

https://mesonet.agron.iastate.edu/geojson/network.py?network=AZOS&only_online=1

"""

import json
from typing import Annotated

from pydantic import Field
from pyiem.database import get_sqlalchemy_conn, sql_helper
from pyiem.webutil import CGIModel, iemapp

from iemweb.fields import CALLBACK_FIELD, NETWORK_FIELD
from iemweb.util import json_response_dict

XREF = {
    "HAS_HML": "HAS_HML",
    "ASOS1MIN": "HAS1MIN",
    "TAF": "HASTAF",
}


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

    callback: CALLBACK_FIELD = None
    has_attribute: Annotated[
        str, Field(description="Station Attribute to filter on")
    ] = None
    network: NETWORK_FIELD = "IA_ASOS"
    only_online: Annotated[
        bool, Field(description="Only include online stations")
    ] = False


def run(conn, network, only_online: bool, has_attribute: str | None):
    """Generate a GeoJSON dump of the provided network"""
    params = {
        "network": network,
    }

    # One off special
    if network in XREF:
        subselect = "a.attr = :network"
        params["network"] = XREF[network]
    elif network == "FPS":
        subselect = """(network ~* 'ASOS' or (
            network ~* 'CLIMATE'and archive_begin < '1990-01-01') or
            network = 'ISUSM')
        and country = 'US' and online"""
    elif network == "AZOS":
        subselect = "network ~* 'ASOS' and online"
    else:
        subselect = "network = :network "
        subselect += "and online" if only_online else ""
        if has_attribute:
            subselect += " and a.attr = :has_attribute"
            params["has_attribute"] = has_attribute
    cursor = conn.execute(
        sql_helper(
            """
    WITH attrs as (
        SELECT t.iemid, string_agg(a.attr, '____') as attrs,
        string_agg(a.value, '____') as attr_values
        from stations t LEFT JOIN station_attributes a
        on (t.iemid = a.iemid) WHERE {subselect}
        GROUP by t.iemid)
    SELECT t.*, ST_AsGeoJSON(t.geom, 4) as geojson,
    coalesce(a.attrs, '') as attrs,
    coalesce(a.attr_values, '') as attr_values from stations t JOIN attrs a on
    (t.iemid = a.iemid) ORDER by t.id ASC
            """,
            subselect=subselect,
        ),
        params,
    )

    res = json_response_dict(
        {
            "type": "FeatureCollection",
            "features": [],
        }
    )

    for row in cursor.mappings():
        ab = row["archive_begin"]
        ae = row["archive_end"]
        time_domain = (
            f"({'????' if ab is None else ab.year}-"
            f"{'Now' if ae is None else ae.year})"
        )
        res["features"].append(
            dict(
                type="Feature",
                id=row["id"],
                properties=dict(
                    elevation=row["elevation"],
                    sname=row["name"],
                    time_domain=time_domain,
                    archive_begin=None if ab is None else f"{ab:%Y-%m-%d}",
                    archive_end=None if ae is None else f"{ae:%Y-%m-%d}",
                    state=row["state"],
                    country=row["country"],
                    climate_site=row["climate_site"],
                    wfo=row["wfo"],
                    tzname=row["tzname"],
                    ncdc81=row["ncdc81"],
                    ncei91=row["ncei91"],
                    ugc_county=row["ugc_county"],
                    ugc_zone=row["ugc_zone"],
                    county=row["county"],
                    sid=row["id"],
                    network=row["network"],
                    online=row["online"],
                    synop=row["synop"],
                    attributes=dict(
                        zip(
                            row["attrs"].split("____"),
                            row["attr_values"].split("____"),
                            strict=False,
                        )
                    ),
                ),
                geometry=json.loads(row["geojson"]),
            )
        )
    res["count"] = len(res["features"])
    return json.dumps(res)


def get_mckey(environ: dict) -> str:
    """Get the memcache key"""
    return (
        "/geojson/network/"
        f"{environ['network']}.geojson|{environ['only_online']}|"
        f"{environ['has_attribute']}/v2"
    )


@iemapp(
    memcachekey=get_mckey,
    memcacheexpire=3600,  # without cache invalidation, an hour is about right
    content_type="application/vnd.geo+json",
    help=__doc__,
    schema=Schema,
)
def application(environ: dict, start_response: callable):
    """Main Workflow"""
    query: Schema = environ["_cgimodel_schema"]

    with get_sqlalchemy_conn("mesosite") as conn:
        res = run(conn, query.network, query.only_online, query.has_attribute)
    start_response("200 OK", [("Content-type", "application/vnd.geo+json")])
    return res
