"""Promo Forecasting — given a SKU + outcome, recommend discount,
duration and weeks based on historical uplifts of similar past promos."""
from __future__ import annotations

from datetime import datetime
from collections import Counter
import numpy as np
import pandas as pd
import streamlit as st

from promo_data import (
    load_all, base_run_rate, past_promos_for_sku,
    parse_unit_size, category_price_benchmark, price_disruptor_multiplier,
    discount_band_uplift_curve, first_time_uplift_cap,
)


def render():
    data = load_all()
    if data["sales"].empty or data["erp"].empty:
        st.error("Missing sales_clean.csv or erp_promo_calendar.csv in ../data/.")
        return

    st.markdown("## 🔮 Promo Forecaster")
    st.caption(
        "Pick a SKU and a desired outcome — the tool digs through past promos "
        "(its own history + sub-category siblings) and recommends discount "
        "depth, duration and best historical campaign types."
    )

    plan = data["plan"]
    if plan.empty:
        st.info("sku_plan_list.csv missing — cannot list SKUs.")
        return

    plan = plan.copy()
    plan["label"] = plan["sku"].astype(str) + " — " + plan.get("name", "").astype(str)

    c1, c2 = st.columns([2, 1])
    pick = c1.selectbox("SKU", plan["label"].tolist(), key="fcr_sku")
    sku = pick.split(" — ")[0]
    outcome = c2.selectbox("Desired outcome",
                            ["📦 Rješavanje lagera", "💰 Veći RUC",
                             "👥 Novi kupci", "🚀 Traffic driver"],
                            key="fcr_outcome")

    name = data["name_map"].get(sku, "")
    cat  = data["cat_map"].get(sku, "")
    tier = data["tier_map"].get(sku, "")
    sub_cat = data["subcat_map"].get(sku, "")
    price = float(data["price_map"].get(sku, 0) or 0)
    cost = float(data["cost_map"].get(sku, 0) or 0)

    st.markdown(f"### {name}")
    st.caption(f"`{sku}` · {cat} · {tier} · sub-cat: *{sub_cat}* · price €{price:.2f}")

    # ---- Past promos for this SKU ----
    own = past_promos_for_sku(data["sales"], data["erp"], sku)

    # ---- Sibling SKUs in same sub-cat ----
    sib_history = []
    if sub_cat and not data["subcat"].empty:
        sib_skus = data["subcat"][(data["subcat"].get("sub_cat", "") == sub_cat)
                                     & (data["subcat"]["sku"].astype(str) != sku)]["sku"].astype(str).tolist()
        for ss in sib_skus[:30]:
            sib_history.extend(past_promos_for_sku(data["sales"], data["erp"], ss))

    if not own and not sib_history:
        st.warning("No promo history found for this SKU or its sub-category siblings.")
        return

    # ---- Recommendation engine ----
    # Combine own + sibling, weight own ×3 vs sibling ×1.
    all_events = [(e, 3) for e in own] + [(e, 1) for e in sib_history]

    valid = [(e, w) for (e, w) in all_events if e["uplift"] > 0]
    if not valid:
        st.warning("Past events found but none with measurable uplift.")
        return

    weights = np.array([w for (_, w) in valid], dtype=float)
    uplifts = np.array([e["uplift"] for (e, _) in valid], dtype=float)
    durations = np.array([e["duration"] for (e, _) in valid], dtype=float)
    post_dips = np.array([e["post_dip_pct"] for (e, _) in valid], dtype=float)

    avg_uplift = float(np.average(uplifts, weights=weights))
    median_uplift = float(np.median(uplifts))
    avg_duration = float(np.average(durations, weights=weights))
    avg_post_dip = float(np.average(post_dips, weights=weights))

    # ---- (1) Anchor discount on the SKU's own historical discount range ----
    # Pull discount % from every backing event (own + sibling) and use the
    # observed min / avg / max to position the outcome's recommendation
    # within what's already worked for this SKU/family. Falls back to fixed
    # constants only if there are too few historical depths to learn from.
    hist_discounts = sorted([
        float(e.get("discount_pct") or 0)
        for (e, _) in valid
        if float(e.get("discount_pct") or 0) > 0
    ])
    has_hist_disc = len(hist_discounts) >= 2
    if has_hist_disc:
        d_min = hist_discounts[0]
        d_max = hist_discounts[-1]
        d_avg = sum(hist_discounts) / len(hist_discounts)
        if "lagera" in outcome.lower():
            rec_discount = int(round(d_max))
            commentary = (f"Stock-clearance — deepest historical depth for this SKU "
                          f"({d_min:.0f}–{d_max:.0f}%, avg {d_avg:.0f}%).")
        elif "ruc" in outcome.lower():
            rec_discount = int(round(d_min))
            commentary = (f"Margin focus — shallowest historical depth for this SKU "
                          f"({d_min:.0f}–{d_max:.0f}%, avg {d_avg:.0f}%).")
        elif "novi" in outcome.lower():
            rec_discount = int(round((d_avg + d_max) / 2))
            commentary = (f"Acquisition — above-avg depth within historical range "
                          f"({d_min:.0f}–{d_max:.0f}%, avg {d_avg:.0f}%).")
        elif "traffic" in outcome.lower():
            rec_discount = int(round(d_avg))
            commentary = (f"Traffic driver — avg historical depth "
                          f"({d_min:.0f}–{d_max:.0f}%, avg {d_avg:.0f}%).")
        else:
            rec_discount = int(round(d_avg))
            commentary = f"Default to avg historical depth ({d_avg:.0f}%)."
    else:
        # Not enough historical depths — keep fixed defaults.
        d_min = d_max = d_avg = 0.0
        if "lagera" in outcome.lower():
            rec_discount = 30
            commentary = "Stock-clearance (default — no historical depths to learn from)."
        elif "ruc" in outcome.lower():
            rec_discount = 12
            commentary = "Margin focus (default — no historical depths to learn from)."
        elif "novi" in outcome.lower():
            rec_discount = 22
            commentary = "Acquisition (default — no historical depths to learn from)."
        elif "traffic" in outcome.lower():
            rec_discount = 25
            commentary = "Traffic driver (default — no historical depths to learn from)."
        else:
            rec_discount = 20
            commentary = "Default."

    # CMs run only monthly (4-week) AKCIJA campaigns.
    rec_duration = 4

    # ---- (3) Breakeven hard-limit ----
    # Predicted uplift grows linearly with discount; breakeven grows faster
    # — so for most SKUs a *shallower* depth is more margin-friendly. If the
    # outcome-anchored discount lands in margin-negative territory, walk it
    # DOWN in 1 % steps until predicted_uplift ≥ breakeven_uplift. Floor is
    # the historical min so we never go below what's been done before. If
    # nothing in the range clears breakeven, flag it.
    # Pre-compute price-zone benchmark once per render — same for all d.
    size_val, size_unit = parse_unit_size(name)
    prices_map = data["price_map"] or {}
    bench = {}
    if size_val > 0 and size_unit and price > 0:
        bench = category_price_benchmark(data["plan"], prices_map, cat, size_unit)
    # Reference band so multiplicative shaping is anchored sensibly.
    ref_band = discount_band_uplift_curve(d_avg if has_hist_disc else 20.0)

    def _pred_uplift_at(d):
        # Start with linear-sensitivity prediction (same family as before).
        base = avg_uplift * (1 + (d - 20) / 100.0 * 0.4)
        # Apply non-linear discount-band shape (delta from reference band).
        band = discount_band_uplift_curve(d)
        if ref_band > 0:
            base *= max(0.5, band / ref_band)
        # Apply price-disruptor multiplier (key for first-time aggressive promos).
        if bench and price > 0 and size_val > 0:
            per100 = price * (1 - d / 100) * 100.0 / size_val
            mult, _zone = price_disruptor_multiplier(per100, bench)
            base *= mult
        # Floor only — no upper cap (caps hide real first-time uplifts).
        return round(max(1.0, base), 2)

    def _breakeven_at(d):
        return (price / (price * (1 - d / 100))) if d < 100 else 999.0

    initial_discount = rec_discount
    floor_d = int(round(d_min)) if has_hist_disc else 0
    breakeven_forced_down = False
    breakeven_unsatisfiable = False

    if _pred_uplift_at(rec_discount) < _breakeven_at(rec_discount):
        new_d = None
        for d in range(rec_discount - 1, floor_d - 1, -1):
            if _pred_uplift_at(d) >= _breakeven_at(d):
                new_d = d
                break
        if new_d is not None:
            rec_discount = new_d
            breakeven_forced_down = True
        else:
            rec_discount = floor_d
            breakeven_unsatisfiable = True

    pred_uplift_at_rec = _pred_uplift_at(rec_discount)
    promo_price = price * (1 - rec_discount / 100)
    breakeven = _breakeven_at(rec_discount)

    # ---- KPIs ----
    st.markdown("#### 📌 Recommendation")
    k1, k2, k3, k4, k5 = st.columns(5)
    k1.metric("Recommended discount", f"{rec_discount}%")
    k2.metric("Recommended duration", f"{rec_duration} weeks")
    k3.metric("Predicted uplift", f"{pred_uplift_at_rec}×")
    k4.metric("Breakeven uplift", f"{breakeven:.2f}×")
    margin = "positive" if pred_uplift_at_rec >= breakeven else "negative"
    k5.metric("Margin signal", margin.upper(),
                help="Predicted uplift vs breakeven uplift.")

    if breakeven_unsatisfiable:
        st.error(
            f"⛔ No margin-positive discount found within this SKU's historical "
            f"range ({d_min:.0f}–{d_max:.0f}%). Even at {floor_d}% predicted uplift "
            f"{_pred_uplift_at(floor_d)}× < breakeven {_breakeven_at(floor_d):.2f}×. "
            "Skipping this promo or renegotiating cost is the right call."
        )
    elif breakeven_forced_down:
        st.info(
            f"🛡️ Discount auto-lowered from **{initial_discount}%** → "
            f"**{rec_discount}%** to clear breakeven. The deeper depth was "
            f"margin-negative on the forecast model."
        )
        st.success(
            f"✅ Predicted uplift {pred_uplift_at_rec}× ≥ breakeven "
            f"{breakeven:.2f}× — margin-positive at {rec_discount}%."
        )
    elif pred_uplift_at_rec >= breakeven:
        st.success(
            f"✅ Predicted uplift {pred_uplift_at_rec}× ≥ breakeven "
            f"{breakeven:.2f}× — margin-positive."
        )
    else:
        # Should not happen given the iteration above, but kept as safety net.
        st.warning(
            f"⚠️ Predicted uplift {pred_uplift_at_rec}× < breakeven "
            f"{breakeven:.2f}× — recommendation is margin-negative."
        )

    st.caption(f"💬 *{commentary}*")

    # ---- Price-zone signal ----
    # Show category-relative price position so CM can sanity-check that the
    # recommendation puts the SKU at the right €/100u shelf position.
    if bench and price > 0 and size_val > 0:
        per100 = price * (1 - rec_discount / 100) * 100.0 / size_val
        disrupt_mult, zone_label = price_disruptor_multiplier(per100, bench)
        if disrupt_mult >= 2.0:
            st.success(
                f"💥 **Price-disruptor zone** — pri {rec_discount}% promo cijena je "
                f"**{per100:.2f} €/100{size_unit}** vs kategorijski p25 "
                f"**{bench['p25']:.2f}** / median **{bench['median']:.2f}** "
                f"(n={bench['n_skus']} SKU u kat.). Uplift dignut ×{disrupt_mult:.2f}."
            )
        elif zone_label:
            st.caption(
                f"📊 Price position: **{per100:.2f} €/100{size_unit}** · "
                f"cat p25 {bench['p25']:.2f} / median {bench['median']:.2f} "
                f"({zone_label}, mult ×{disrupt_mult:.2f})."
            )

    # ---- Volume / revenue / RUC forecast ----
    st.markdown("#### 📊 Forecast — units, revenue, RUC")
    rr = base_run_rate(data["sales"], data["erp"], sku, lookback=26)
    base_avg = rr["avg"]
    promo_qty = base_avg * rec_duration * pred_uplift_at_rec
    base_qty = base_avg * rec_duration
    incremental_qty = promo_qty - base_qty

    promo_revenue = promo_qty * promo_price
    base_revenue = base_qty * price
    rev_delta = promo_revenue - base_revenue

    promo_ruc_unit = max(0.0, promo_price - cost)
    base_ruc_unit = max(0.0, price - cost)
    promo_ruc = promo_qty * promo_ruc_unit
    base_ruc = base_qty * base_ruc_unit
    ruc_delta = promo_ruc - base_ruc

    f1, f2, f3, f4 = st.columns(4)
    f1.metric("Baseline / wk", f"{base_avg:.0f}",
               help="Mean of top half of clean non-promo weeks in last 26 weeks "
                    "(RCM + WEB only). Excludes OOS / promo / post-promo bleed weeks.")
    f2.metric("Forecast units",
               f"{int(promo_qty):,}",
               delta=f"{int(incremental_qty):+,} vs no-promo",
               help=f"{int(base_qty):,} baseline × {pred_uplift_at_rec}× predicted uplift "
                    f"over {rec_duration} weeks.")
    f3.metric("Forecast revenue",
               f"€{promo_revenue:,.0f}",
               delta=f"€{rev_delta:+,.0f} vs no-promo",
               help=f"Promo price €{promo_price:.2f} × forecast units.")
    f4.metric("Forecast RUC",
               f"€{promo_ruc:,.0f}",
               delta=f"€{ruc_delta:+,.0f} vs no-promo",
               help=f"Unit RUC at promo price: €{promo_ruc_unit:.2f} "
                    f"(vs €{base_ruc_unit:.2f} at full price).")

    if base_avg <= 0:
        st.caption("⚠️ Baseline run-rate is 0 — forecast may be unreliable; "
                   "review past promos and override manually in Planner.")

    # ---- Discount sensitivity ----
    st.markdown("#### 📈 Discount sensitivity")
    discounts = list(range(0, 51, 5))
    pred_uplifts = [round(min(avg_uplift * (1 + (d - 20) / 100.0 * 0.4), 2.5), 2) for d in discounts]
    breakevens   = [(price / (price * (1 - d / 100))) if d < 100 else 999 for d in discounts]
    sens = pd.DataFrame({
        "Discount %": discounts,
        "Predicted uplift": pred_uplifts,
        "Breakeven uplift": [round(b, 2) for b in breakevens],
        "Margin OK?": ["✅" if u >= b else "❌"
                          for u, b in zip(pred_uplifts, breakevens)],
    })
    st.dataframe(sens, use_container_width=True, hide_index=True)

    # ---- Past events used ----
    st.markdown("#### 📜 Historical events backing this recommendation")
    rows = []
    for e, w in valid[:25]:
        is_own = "🟢 own" if w >= 3 else "⚪ sibling"
        rows.append({
            "Source": is_own,
            "Type": e["type"],
            "Year": e["year"],
            "Period": f"CW{e['start_week']}–CW{e['end_week']}",
            "Duration (w)": e["duration"],
            "Avg base/wk": e["avg_base"],
            "Avg promo/wk": e["avg_promo"],
            "Uplift": f"{e['uplift']}×",
            "Post-dip %": f"{e['post_dip_pct']:+.0f}%",
        })
    st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True)

    # ---- Stats summary ----
    st.markdown("#### 📊 Aggregated history stats")
    a1, a2, a3, a4 = st.columns(4)
    a1.metric("Events", len(valid))
    a2.metric("Avg historical uplift", f"{avg_uplift:.2f}×")
    a3.metric("Avg duration", f"{avg_duration:.1f} weeks")
    a4.metric("Avg post-dip", f"{avg_post_dip:+.1f}%")

    # Top promo types historically
    type_counts = Counter(e["type"] for (e, _) in valid)
    top_types = type_counts.most_common(5)
    if top_types:
        st.caption("**Top historical promo types** (by count): "
                    + " · ".join([f"{t} ({n})" for t, n in top_types]))
