""".. title:: NWS Miscellaneous Polygons

Return to `User Frontend </request/gis/misc.phtml>`_ or
`IEM API Services </api/#cgi>`_.

Documentation for /cgi-bin/request/gis/misc.py
-------------------------------------------------

This service emits polygons associated with miscellaneous NWS products, not
including SPS or those with VTEC.  The output formats are Shapefile, KML,
CSV, or Excel.  The default output format is Shapefile.

Changelog
---------

- 2024-10-31: Initial implementation.

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

Provide all miscellaneous polygons for October 2024 in shapefile and then KML
and then CSV and then Excel.

https://mesonet.agron.iastate.edu/cgi-bin/request/gis/misc.py\
?sts=2024-10-01T00:00Z&ets=2024-11-01T00:00Z

https://mesonet.agron.iastate.edu/cgi-bin/request/gis/misc.py\
?sts=2024-10-01T00:00Z&ets=2024-11-01T00:00Z&format=kml

https://mesonet.agron.iastate.edu/cgi-bin/request/gis/misc.py\
?sts=2024-10-01T00:00Z&ets=2024-11-01T00:00Z&format=csv

https://mesonet.agron.iastate.edu/cgi-bin/request/gis/misc.py\
?sts=2024-10-01T00:00Z&ets=2024-11-01T00:00Z&format=excel

"""

import tempfile
import zipfile
from io import BytesIO, StringIO
from typing import Annotated

import fiona
import geopandas as gpd
import pandas as pd
from pydantic import AwareDatetime, Field
from pyiem.database import get_sqlalchemy_conn, sql_helper
from pyiem.exceptions import IncompleteWebRequest
from pyiem.reference import ISO8601
from pyiem.webutil import CGIModel, iemapp

from iemweb.fields import (
    DAY_OF_MONTH_FIELD_OPTIONAL,
    HOUR_FIELD,
    MINUTE_FIELD,
    MONTH_FIELD_OPTIONAL,
    YEAR_FIELD_OPTIONAL,
)

fiona.supported_drivers["KML"] = "rw"
PRJFILE = "/opt/iem/data/gis/meta/4326.prj"
EXL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"


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

    format: Annotated[
        str,
        Field(
            description="Output format, either shp, kml, csv, or excel",
            pattern="^(shp|kml|csv|excel)$",
        ),
    ] = "shp"
    sts: AwareDatetime = Field(default=None, description="Start Time")
    ets: AwareDatetime = Field(default=None, description="End Time")
    year1: YEAR_FIELD_OPTIONAL = None
    month1: MONTH_FIELD_OPTIONAL = None
    day1: DAY_OF_MONTH_FIELD_OPTIONAL = None
    hour1: HOUR_FIELD = 0
    minute1: MINUTE_FIELD = 0
    year2: YEAR_FIELD_OPTIONAL = None
    month2: MONTH_FIELD_OPTIONAL = None
    day2: DAY_OF_MONTH_FIELD_OPTIONAL = None
    hour2: HOUR_FIELD = 0
    minute2: MINUTE_FIELD = 0


def run(query: Schema, start_response: callable):
    """Do something!"""
    with get_sqlalchemy_conn("postgis") as conn:
        df = gpd.read_postgis(
            sql_helper("""
                select pil, geom,
                issue at time zone 'UTC' as issue,
                expire at time zone 'UTC' as expire,
                product_id as PROD_ID
                from text_products WHERE issue >= :sts and
                issue < :ets ORDER by issue ASC
                 """),
            conn,
            params={
                "sts": query.sts,
                "ets": query.ets,
            },
            geom_col="geom",
        )  # type: ignore
    if df.empty:
        start_response("200 OK", [("Content-type", "text/plain")])
        return b"ERROR: no results found for your query"
    for col in ["issue", "expire"]:
        df[col] = df[col].dt.strftime(ISO8601)
    df.columns = [s.upper() if s != "geom" else "geom" for s in df.columns]
    fn = f"misc_{query.sts:%Y%m%d%H%M}_{query.ets:%Y%m%d%H%M}"
    if query.format == "kml":
        fp = BytesIO()
        with fiona.Env():
            df.to_file(fp, driver="KML", NameField="NAME", engine="fiona")
        headers = [
            ("Content-type", "application/octet-stream"),
            ("Content-Disposition", f"attachment; filename={fn}.kml"),
        ]
        start_response("200 OK", headers)
        return fp.getvalue()
    if query.format == "csv":
        fp = StringIO()
        df.drop(columns="geom").to_csv(fp, index=False)
        headers = [
            ("Content-type", "application/octet-stream"),
            ("Content-Disposition", f"attachment; filename={fn}.csv"),
        ]
        start_response("200 OK", headers)
        return fp.getvalue().encode("ascii")
    if query.format == "excel":
        fp = BytesIO()
        with pd.ExcelWriter(fp) as writer:
            df.drop(columns="geom").to_excel(writer, index=False)
        headers = [
            ("Content-type", EXL),
            ("Content-Disposition", f"attachment; filename={fn}.xlsx"),
        ]
        start_response("200 OK", headers)
        return fp.getvalue()

    schema = {
        "geometry": "Polygon",
        "properties": {
            "PIL": "str:6",
            "ISSUE": "str:20",
            "EXPIRE": "str:20",
            "PROD_ID": "str:36",
        },
    }
    with tempfile.TemporaryDirectory() as tmpdir:
        df.to_file(f"{tmpdir}/{fn}.shp", schema=schema, engine="fiona")

        zio = BytesIO()
        with zipfile.ZipFile(
            zio, mode="w", compression=zipfile.ZIP_DEFLATED
        ) as zf:
            with open(PRJFILE, encoding="utf-8") as fh:
                zf.writestr(f"{fn}.prj", fh.read())
            for suffix in ["shp", "shx", "dbf"]:
                zf.write(f"{tmpdir}/{fn}.{suffix}", f"{fn}.{suffix}")
    headers = [
        ("Content-type", "application/octet-stream"),
        ("Content-Disposition", f"attachment; filename={fn}.zip"),
    ]
    start_response("200 OK", headers)

    return zio.getvalue()


@iemapp(default_tz="UTC", help=__doc__, schema=Schema)
def application(environ: dict, start_response: callable):
    """Do something fun!"""
    query: Schema = environ["_cgimodel_schema"]
    if query.sts is None or query.ets is None:
        raise IncompleteWebRequest("GET start or end time parameters missing")
    return [run(query, start_response)]
