"""
Daily plot of observed daily summary variables
along with the daily climatology for the nearest (sometimes same) location.
The vertical highlighted stripes on the plot are just the weekend dates.
"""

import warnings
from datetime import date, timedelta

import httpx
import matplotlib.patheffects as PathEffects
import numpy as np
import pandas as pd
from matplotlib.patches import Rectangle
from pyiem.database import get_sqlalchemy_conn, sql_helper
from pyiem.exceptions import BadWebRequest, NoDataFound
from pyiem.plot import figure_axes

warnings.simplefilter("ignore", UserWarning)
PDICT = {
    "temps": "High/Low Temperature",
    "dwpf": "Dew Point Temperature",
    "feel": "Feels Like Temperature",
    "precip": "Precipitation",
}


def get_description():
    """Return a dict describing how to call this plotter"""
    desc = {"description": __doc__, "data": True, "cache": 300}
    today = date.today()
    mo = today.month
    yr = today.year
    desc["arguments"] = [
        dict(
            type="zstation",
            name="station",
            default="AMW",
            network="IA_ASOS",
            label="Select Station:",
        ),
        dict(type="month", name="month", default=mo, label="Select Month"),
        dict(
            type="year",
            name="year",
            default=yr,
            label="Select Year",
            min=1850,
            max=today.year,
        ),
        dict(
            type="select",
            options=PDICT,
            default="temps",
            label="Select Plot Type:",
            name="p",
        ),
    ]
    return desc


def common(ctx):
    """Do things common to both plots."""
    station = ctx["station"]
    year = ctx["year"]
    if not ctx["network"].endswith("CLIMATE") and year < 1900:
        raise BadWebRequest("No data available for year before 1900.")
    month = ctx["month"]

    sts = date(year, month, 1)
    ets = sts + timedelta(days=35)
    ets = ets.replace(day=1)
    days = int((ets - sts).days)
    weekends = []
    now = sts
    while now < ets:
        if now.weekday() in [5, 6]:
            weekends.append(now.day)
        now += timedelta(days=1)
    try:
        resp = httpx.get(
            "http://mesonet.agron.iastate.edu/api/1/daily.json",
            params={
                "station": station,
                "network": ctx["network"],
                "year": year,
                "month": month,
            },
            timeout=15,
        )
        resp.raise_for_status()
    except Exception as exp:
        raise NoDataFound("Unable to fetch data from API service.") from exp
    jsn = resp.json()
    df = pd.DataFrame(jsn["data"])
    if not df.empty:
        df["day_of_month"] = pd.to_datetime(df["date"]).dt.day
        df["accum_pday"] = df["precip"].cumsum()
        df = df.set_index("day_of_month")
    # Special case of climate districts and statewide avgs
    table = "ncei_climate91"
    clcol = "ncei91"
    if ctx["network"].endswith("CLIMATE") and (
        station.endswith("0000") or station[2] == "C"
    ):
        table = "climate"
        clcol = "climate_site"
        subtitle = "Climatology provided by period of record averages"
    else:
        if ctx["_nt"].sts[station]["ncei91"] is None:
            subtitle = "Daily climatology unavailable for site"
        else:
            subtitle = (
                "NCEI 1991-2020 Climate Site: "
                f"{ctx['_nt'].sts[station]['ncei91']}"
            )
    if ctx["p"] in ("dwpf", "feel"):
        subtitle = "Climatology is a simple 7 day smoothed POR average"
    # Get the normals
    with get_sqlalchemy_conn("coop") as conn:
        cdf = pd.read_sql(
            sql_helper(
                """
    SELECT high as climo_high, low as climo_low,
    extract(day from valid)::int as day_of_month,
    precip as climo_precip from {table} where station = :station and
    extract(month from valid) = :month ORDER by valid ASC""",
                table=table,
            ),
            conn,
            params={"station": ctx["_nt"].sts[station][clcol], "month": month},
            index_col="day_of_month",
        )
    df = cdf.join(df, how="outer")
    df["accum_climo_precip"] = df["climo_precip"].cumsum()
    if "accum_pday" not in df.columns:
        df["accum_pday"] = 0
    df["depart_precip"] = df["accum_pday"] - df["accum_climo_precip"]
    title = f"{ctx['_sname']} :: {PDICT[ctx['p']]} for {sts:%b %Y}\n{subtitle}"
    (ctx["fig"], ax) = figure_axes(title=title, apctx=ctx)
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width, box.height * 0.94])

    for day in weekends:
        rect = Rectangle(
            [day - 0.5, -100], 1, 300, facecolor="#EEEEEE", edgecolor="None"
        )
        ax.add_patch(rect)
    ax.set_xlim(0.5, days + 0.5)
    ax.set_xticks(range(1, days + 1))
    ax.set_xticklabels(np.arange(1, days + 1))
    ax.set_xlabel(sts.strftime("%B %Y"), fontsize="larger")
    ax.yaxis.grid(linestyle="-")
    ctx["df"] = df


def do_precip_plot(ctx) -> bool:
    """Make the precipitation plot."""
    hasdata = False
    ax = ctx["fig"].gca()
    df = ctx["df"]
    ymax = 1
    ymin = 0
    if df["accum_climo_precip"].max() > 0:
        hasdata = True
        ax.plot(
            df.index.values,
            df["accum_climo_precip"].values,
            zorder=3,
            color="r",
            label="Accum Avg",
        )
        ymax = df["accum_climo_precip"].max() + 0.5
    if df["depart_precip"].notnull().any():
        hasdata = True
        ax.bar(
            df.index.values - 0.2,
            df["depart_precip"].values,
            width=0.4,
            align="center",
            zorder=3,
            color="r",
            label="Accum Diff",
        )
        ymin = min([0, df["depart_precip"].min() - 0.5])
    if "precip" in df.columns and df["precip"].notnull().any():
        hasdata = True
        ax.bar(
            df.index.values + 0.2,
            df["precip"].values,
            width=0.4,
            align="center",
            zorder=3,
            color="b",
            label="Daily Precip",
        )
        ax.plot(
            df.index.values,
            df["accum_pday"].values,
            zorder=4,
            color="b",
            label="Accum Obs",
        )
        ymax = max([ymax, df["accum_pday"].max() + 0.5])
    ax.set_ylabel("Precipitation [inch]", fontsize="large")
    ax.set_ylim(ymin, ymax)
    return hasdata


def do_temperature_plot(ctx) -> bool:
    """Make the temperature plot."""
    ax = ctx["fig"].gca()
    df = ctx["df"]
    hasdata = False
    if df["climo_high"].notnull().any():
        hasdata = True
        ax.plot(
            df.index.values,
            df["climo_high"].values,
            zorder=3,
            marker="o",
            color="pink",
            label="Climate High",
        )
        ax.plot(
            df.index.values,
            df["climo_low"].values,
            zorder=3,
            marker="o",
            color="skyblue",
            label="Climate Low",
        )
    if "max_tmpf" in df.columns and not all(pd.isnull(df["max_tmpf"])):
        hasdata = True
        ax.bar(
            df.index.values - 0.3,
            df["max_tmpf"].values,
            fc="r",
            ec="k",
            width=0.3,
            linewidth=0.6,
            label="Ob High",
        )
        if "min_tmpf" in df.columns and not all(pd.isnull(df["min_tmpf"])):
            ax.bar(
                df.index.values,
                df["min_tmpf"].values,
                fc="b",
                ec="k",
                width=0.3,
                linewidth=0.6,
                label="Ob Low",
            )
    else:
        ax.text(0.5, 0.5, "No Data Found", transform=ax.transAxes, ha="center")
        ax.set_ylim(0, 1)

    i = 0
    if "max_tmpf" in df.columns and not all(pd.isnull(df["max_tmpf"])):
        for _, row in df.iterrows():
            if pd.isna(row["max_tmpf"]) or pd.isna(row["min_tmpf"]):
                i += 1
                continue
            txt = ax.text(
                i + 1 - 0.15,
                row["max_tmpf"] + 0.5,
                f"{row['max_tmpf']:.0f}",
                ha="center",
                va="bottom",
                color="k",
            )
            txt.set_path_effects(
                [PathEffects.withStroke(linewidth=2, foreground="w")]
            )
            txt = ax.text(
                i + 1 + 0.15,
                row["min_tmpf"] + 0.5,
                f"{row['min_tmpf']:.0f}",
                ha="center",
                va="bottom",
                color="k",
            )
            txt.set_path_effects(
                [PathEffects.withStroke(linewidth=2, foreground="w")]
            )
            i += 1
        if df["min_tmpf"].notnull().any():
            ax.set_ylim(
                np.nanmin([df["climo_low"].min(), df["min_tmpf"].min()]) - 5,
                np.nanmax([df["climo_high"].max(), df["max_tmpf"].max()]) + 5,
            )
    ax.set_ylabel("Temperature °F")
    return hasdata


def do_dwpf_plot(ctx) -> bool:
    """Make the dew point temperature plot."""
    ax = ctx["fig"].gca()
    df = ctx["df"]
    hasdata = False
    with get_sqlalchemy_conn("iem") as conn:
        climodf = pd.read_sql(
            sql_helper(
                """select extract(day from day) as day_of_month,
                avg(max_dwpf) as climo_max_dwpf,
                avg(min_dwpf) as climo_min_dwpf,
                avg(max_feel) as climo_max_feel,
                avg(min_feel) as climo_min_feel
                from summary a JOIN stations t
                on (a.iemid = t.iemid) where t.network = :network and
                t.id = :station and extract(month from day) = :month
                group by day_of_month order by day_of_month""",
            ),
            conn,
            params={
                "network": ctx["network"],
                "station": ctx["station"],
                "month": ctx["month"],
            },
            index_col="day_of_month",
        )
        if not climodf.empty:
            # Smooth the data 7 days
            climodf[f"climo_max_{ctx['p']}"] = (
                climodf[f"climo_max_{ctx['p']}"]
                .rolling(7, min_periods=1)
                .mean()
                .round(1)
            )
            climodf[f"climo_min_{ctx['p']}"] = (
                climodf[f"climo_min_{ctx['p']}"]
                .rolling(7, min_periods=1)
                .mean()
                .round(1)
            )
    if climodf[f"climo_max_{ctx['p']}"].notnull().any():
        hasdata = True
        ax.plot(
            climodf.index.values,
            climodf[f"climo_max_{ctx['p']}"].values,
            zorder=3,
            marker="o",
            color="pink",
            label="Climate High",
        )
        ax.plot(
            climodf.index.values,
            climodf[f"climo_min_{ctx['p']}"].values,
            zorder=3,
            marker="o",
            color="skyblue",
            label="Climate Low",
        )
    if f"max_{ctx['p']}" in df.columns and not all(
        pd.isnull(df[f"max_{ctx['p']}"])
    ):
        hasdata = True
        ax.bar(
            df.index.values - 0.3,
            df[f"max_{ctx['p']}"].values,
            fc="r",
            ec="k",
            width=0.3,
            linewidth=0.6,
            label="Ob High",
        )
        if f"min_{ctx['p']}" in df.columns and not all(
            pd.isnull(df[f"min_{ctx['p']}"])
        ):
            ax.bar(
                df.index.values,
                df[f"min_{ctx['p']}"].values,
                fc="b",
                ec="k",
                width=0.3,
                linewidth=0.6,
                label="Ob Low",
            )
    else:
        ax.text(0.5, 0.5, "No Data Found", transform=ax.transAxes, ha="center")
        ax.set_ylim(0, 1)

    i = 0
    if f"max_{ctx['p']}" in df.columns and not all(
        pd.isnull(df[f"max_{ctx['p']}"])
    ):
        for _, row in df.iterrows():
            if pd.isna(row[f"max_{ctx['p']}"]) or pd.isna(
                row[f"min_{ctx['p']}"]
            ):
                i += 1
                continue
            col = row[f"max_{ctx['p']}"]
            txt = ax.text(
                i + 1 - 0.15,
                col + 0.5,
                f"{col:.0f}",
                ha="center",
                va="bottom",
                color="k",
            )
            txt.set_path_effects(
                [PathEffects.withStroke(linewidth=2, foreground="w")]
            )
            col = row[f"min_{ctx['p']}"]
            txt = ax.text(
                i + 1 + 0.15,
                col + 0.5,
                f"{col:.0f}",
                ha="center",
                va="bottom",
                color="k",
            )
            txt.set_path_effects(
                [PathEffects.withStroke(linewidth=2, foreground="w")]
            )
            i += 1
        if df[f"min_{ctx['p']}"].notnull().any():
            ax.set_ylim(
                np.nanmin(
                    [
                        climodf[f"climo_min_{ctx['p']}"].min(),
                        df[f"min_{ctx['p']}"].min(),
                    ]
                )
                - 5,
                np.nanmax(
                    [
                        climodf[f"climo_max_{ctx['p']}"].max(),
                        df[f"max_{ctx['p']}"].max(),
                    ]
                )
                + 5,
            )
    ax.set_ylabel(f"{PDICT[ctx['p']]} °F")
    return hasdata


def plotter(ctx: dict):
    """Go"""
    common(ctx)
    if ctx["p"] == "precip":
        hasdata = do_precip_plot(ctx)
    elif ctx["p"] in ("dwpf", "feel"):
        hasdata = do_dwpf_plot(ctx)
    else:
        hasdata = do_temperature_plot(ctx)
    # Prevent matplotlib warning about no artists
    if hasdata:
        ctx["fig"].gca().legend(
            bbox_to_anchor=(0.0, 1.01, 1.0, 0.102),
            loc=3,
            ncol=4,
            mode="expand",
            borderaxespad=0.0,
        )
    return ctx["fig"], ctx["df"]
