"""
This chart displays the period each year between the first and last SPC/WPC
outlook type for a given NWS forecast office, state or county.
The dates presented
are the commonly considered date of outlook being valid for.  Technically,
the outlooks end at 12 UTC of the following day.</p>

<p>If you select the "View by Contiguous US" option, there is no spatial
overlay check possible at this time.</p>

<p>An option exists to threshold the amount of spatial overlap between the
outlook geometry and the NWS forecast office or state.  This is useful for
the situation of avoiding sliver touches or overlaps that aren't of perhaps
significance.  There is an "engulfs" option, that requires a 99% overlap. A
100% overlap is difficult for certain situations and this may generally be
difficult for offices that have marine zones.</p>

<p>Autoplot <a href="/plotting/auto/?q=200">200</a> is similar, but generates
spatial heatmaps.</p>
"""

import calendar
from datetime import date, timedelta

import numpy as np
import pandas as pd
from matplotlib.artist import setp
from matplotlib.ticker import MaxNLocator
from pyiem import reference
from pyiem.database import get_dbconnc, get_sqlalchemy_conn, sql_helper
from pyiem.exceptions import NoDataFound
from pyiem.plot import figure

from iemweb.autoplot import ARG_FEMA, get_monofont
from iemweb.autoplot.scripts200.p200 import OUTLOOKS

PDICT = {
    "0": "At least touches",
    "5": "At least 5% overlap",
    "50": "At least 50% overlap",
    "99": "Engulfs",
}
PDICT2 = {
    "conus": "View by Contiguous US",
    "wfo": "View by Single NWS Forecast Office",
    "state": "View by State",
    "ugc": "View by Selected County/Zone",
    "fema": "View by FEMA Region",
}
PDICT3 = {
    "C": "SPC Convective Outlook",
    "F": "SPC Fire Weather Outlook",
    "E": "WPC Excessive Rainfall Outlook",
}
PDICT4 = {
    "1": "1",
    "2": "2",
    "3": "3",
    "4": "4",
    "5": "5",
    "6": "6",
    "7": "7",
    "8": "8",
}


def get_description():
    """Return a dict describing how to call this plotter"""
    desc = {"description": __doc__, "data": True, "cache": 86400}
    desc["arguments"] = [
        {
            "type": "year",
            "name": "year",
            "default": 1986,
            "label": "Start Year of Plot:",
            "min": 1986,
        },
        {
            "type": "select",
            "options": PDICT3,
            "default": "C",
            "label": "Select Outlook Type:",
            "name": "outlook_type",
        },
        {
            "type": "select",
            "options": OUTLOOKS,
            "default": "CATEGORICAL.SLGT",
            "label": "Select Outlook Threshold:",
            "name": "threshold",
        },
        {
            "type": "select",
            "options": PDICT4,
            "default": "1",
            "label": "Select Day:",
            "name": "day",
        },
        dict(
            type="select",
            name="opt",
            default="wfo",
            options=PDICT2,
            label="View by WFO, State or Zone?",
        ),
        dict(
            type="networkselect",
            name="wfo",
            network="WFO",
            default="DMX",
            label="Select WFO:",
        ),
        ARG_FEMA,
        dict(type="state", default="IA", name="state", label="Select State:"),
        {
            "type": "ugc",
            "name": "ugc",
            "default": "IAZ048",
            "label": "Select County/Zone:",
        },
        {
            "type": "select",
            "options": PDICT,
            "default": "0",
            "label": (
                "Select Spatial Overlap Threshold:<br />"
                "<i>Warning: overlap percentage adds 10s of "
                "seconds to plot generation</i>"
            ),
            "name": "overlap",
        },
    ]
    return desc


def plotter(ctx: dict):
    """Go"""
    wfo = ctx["wfo"][:4]
    opt = ctx["opt"]
    overlap = ctx["overlap"]
    state = ctx["state"]
    ugc = ctx["ugc"]
    fema = ctx["fema"]
    (category, threshold) = ctx["threshold"].split(".", 1)

    params = {
        "wfo": wfo,
        "state": state,
        "fema": fema,
        "overlap": int(overlap) / 100.0,
        "threshold": threshold,
        "category": category,
        "day": ctx["day"],
        "ugc": ugc,
        "sts": date(ctx["year"], 1, 1),
        "outlook_type": ctx["outlook_type"].upper(),
    }
    geomtable = "cwa"
    limiter = "g.wfo = :wfo"
    geomcol = "the_geom"
    ugcname = ""
    if opt == "state":
        geomtable = "states"
        limiter = "g.state_abbr = :state"
    elif opt == "fema":
        geomcol = "geom"
        geomtable = "fema_regions"
        limiter = "g.region = :fema"
    elif opt == "ugc":
        geomtable = "ugcs"
        limiter = "g.ugc = :ugc and end_ts is null"
        geomcol = "geom"
        pgconn, cursor = get_dbconnc("postgis")
        cursor.execute(
            "select name from ugcs where ugc = %s and end_ts is null",
            (ugc,),
        )
        ugcname = cursor.fetchone()["name"]
        cursor.close()
        pgconn.close()
    overlapsql = ""
    if overlap != "0":
        overlapsql = (
            f"and ST_Area(ST_Intersection(o.geom, g.{geomcol})::geography) / "
            f"(select ST_Area({geomcol}::geography) from {geomtable} g "
            f"where {limiter} limit 1) > :overlap"
        )
    with get_sqlalchemy_conn("postgis") as conn:
        if opt == "conus":
            df = pd.read_sql(
                sql_helper(
                    """
                select distinct date(expire - '12 hours'::interval) as date
                from spc_outlooks o WHERE o.outlook_type = :outlook_type and
                o.day = :day
                and o.threshold = :threshold and o.category = :category and
                o.expire > :sts
                """
                ),
                conn,
                params=params,
                index_col=None,
                parse_dates="date",
            )

        else:
            df = pd.read_sql(
                sql_helper(
                    """
                select distinct date(expire - '12 hours'::interval) as date
                from spc_outlooks o, {table} g WHERE {limiter}
                and o.outlook_type = :outlook_type and o.day = :day
                and o.threshold = :threshold and o.category = :category and
                ST_Intersects(o.geom, g.{geomcol}) {overlapsql} and
                o.expire > :sts
                """,
                    table=geomtable,
                    geomcol=geomcol,
                    limiter=limiter,
                    overlapsql=overlapsql,
                ),
                conn,
                params=params,
                index_col=None,
                parse_dates="date",
            )
    if df.empty:
        raise NoDataFound("No data found for query")
    df["doy"] = df["date"].dt.dayofyear
    df["year"] = df["date"].dt.year

    tt = OUTLOOKS[ctx["threshold"]].split("(")[0].strip()
    title = (
        f"{'WPC' if ctx['outlook_type'] == 'E' else 'SPC'} Day "
        f"{ctx['day']} {tt} Outlook"
    )
    verb = "Intersects" if overlap != "99" else "Engulfs"
    subtitle = (
        f"Based on Unofficial IEM Archives, {verb} [{wfo}] "
        f"NWS {ctx['_nt'].sts[wfo]['name']} CWA"
    )
    if opt == "state":
        subtitle = f"{verb} State of {reference.state_names[state]}"
    elif opt == "ugc":
        subtitle = f"{verb} County/Zone {ugcname} [{ugc}]"
    elif opt == "conus":
        subtitle = "Based on Unofficial IEM Archives, Contiguous US"
    elif opt == "fema":
        subtitle = f"{verb} FEMA Region {fema}"
    if ctx["overlap"] not in ["0", "99"]:
        subtitle += f" with >= {overlap}% overlap"
    fig = figure(apctx=ctx, title=title, subtitle=subtitle)
    ax = fig.add_axes((0.08, 0.1, 0.58, 0.8))
    monofont = get_monofont()
    for year, gdf in df.groupby("year"):
        size = max(gdf["doy"].max() - gdf["doy"].min(), 1)
        ax.barh(
            year,
            size,
            left=gdf["doy"].min(),
            ec="brown",
            fc="tan",
            align="center",
        )
        ax.barh(
            gdf["year"].values,
            [1] * len(gdf.index),
            left=gdf["doy"].values,
            zorder=3,
            color="r",
        )
        # Add a label for the year on the RHS of ax
        ax.annotate(
            f"{gdf['date'].min():%b %d} - {gdf['date'].max():%b %d, %Y}",
            xy=(1.01, year),
            xycoords=("axes fraction", "data"),
            va="center",
            ha="left",
            color="k",
            fontproperties=monofont,
        )
    gdf = df[["year", "doy"]].groupby("year").agg(["min", "max"])
    if len(gdf.index) > 2:
        # Exclude first and last year in the average
        avg_start = np.average(gdf["doy", "min"].to_numpy()[1:-1])
        avg_end = np.average(gdf["doy", "max"].to_numpy()[1:-1])
        ax.axvline(avg_start, ls=":", lw=2, color="k")
        ax.axvline(avg_end, ls=":", lw=2, color="k")
        x0 = date(2000, 1, 1)
        _1 = (x0 + timedelta(days=int(avg_start))).strftime("%-d %b")
        _2 = (x0 + timedelta(days=int(avg_end))).strftime("%-d %b")
        ax.set_xlabel(f"Average Start Date: {_1}, End Date: {_2}")
    ax.grid()
    xticks = [1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
    ax.set_xticks(xticks)
    ax.set_xticklabels(calendar.month_abbr[1:])
    ax.set_xlim(1, 367)
    ax.set_ylabel("Year")
    ax.set_ylim(df["year"].min() - 0.5, df["year"].max() + 0.5)
    ax.yaxis.set_major_locator(MaxNLocator(integer=True))

    # __________________________________________
    ax = fig.add_axes((0.86, 0.1, 0.1, 0.8))
    gdf = df[["year", "doy"]].groupby("year").count()
    ax.barh(gdf.index.values, gdf["doy"].values, fc="blue", align="center")
    for year, val in zip(gdf.index.values, gdf["doy"].values, strict=False):
        ax.annotate(
            f"{val}",
            xy=(1.01, year),
            xycoords=("axes fraction", "data"),
            va="center",
            ha="left",
            fontproperties=monofont,
        )
    ax.set_ylim(df["year"].min() - 0.5, df["year"].max() + 0.5)
    setp(ax.get_yticklabels(), visible=False)
    ax.grid(True)
    ax.set_xlabel("# Days")
    ax.xaxis.set_major_locator(MaxNLocator(3, integer=True))

    return fig, df
