"""Marketing-mode data loaders & aggregations for the Promo Tool.

Source: webshop coupon order log exported from Magento, placed in
PromoTool/data/. One row per (order × coupon × product), with timestamp.
Cancelled orders ("08 - Otkazano") are excluded.

The CM/Nabava side is driven by erp_promo_calendar.csv (weekly windows).
Marketing is driven by this coupon log (daily granularity, real discounts,
webshop channel only).
"""
from __future__ import annotations

import re
from pathlib import Path

import pandas as pd
import streamlit as st

PROMO_DIR = Path(__file__).parent / "data"
COUPON_FILE = PROMO_DIR / "Detaljni report jedan red po orderu, kuponu i proizvodu..csv"

CANCELLED_STATUS = "08 - Otkazano"


def classify_campaign(coupon_name: str, coupon_code: str) -> str:
    """Map a coupon to a marketing-campaign label. Same regex as in the
    promo_weeks_flag analysis — kept in one place so it can evolve."""
    s = f"{coupon_name or ''} {coupon_code or ''}".upper()
    if "BF2025" in s or "BF 25" in s or "BF25" in s or "BLACK FRIDAY" in s:
        return "BF 2025"
    if "XMAS2025" in s or "XMAS 2025" in s or "PROSINAC25" in s or "XMAS" in s:
        return "XMAS 2025"
    if "BDAY2026" in s or "BD2026" in s or "PLAZA18" in s or "BOSS18" in s:
        return "BDAY 2026"
    if "WOMEN'S WEEK" in s or "ZOEWW" in s or "EXTRA15WMN" in s:
        return "Women's Week 2026"
    if "WINTER" in s or "WS26" in s:
        return "Winter Sale 2026"
    if "01052026" in s or "1.5.2026" in s:
        return "1. svibnja 2026"
    if "FLASH" in s:
        return "Flash sale"
    if "POLLEO WEAR" in s or "VULE20" in s:
        return "Polleo Wear"
    # POLLEO15 must come BEFORE SNACK — its description contains "Snacks & RTD"
    if "POLLEO15" in s:
        return "POLLEO15 -15%"
    if "SNACK" in s:
        return "Snacks -15%"
    if "ZOE" in s:
        return "ZOE promo"
    return "Ostalo"


@st.cache_data(ttl=3600)
def load_coupons() -> pd.DataFrame:
    """Read the Magento coupon order log. Returns empty DataFrame if
    the file is missing. Cancelled orders are filtered out here so
    every downstream caller sees only valid sales."""
    if not COUPON_FILE.exists():
        return pd.DataFrame()
    df = pd.read_csv(COUPON_FILE, sep=";", encoding="cp1250")
    df.columns = [str(c).strip().lower() for c in df.columns]
    if "order_status" in df.columns:
        df = df[df["order_status"] != CANCELLED_STATUS].copy()
    df["date_added"] = pd.to_datetime(df["date_added"], errors="coerce")
    df = df.dropna(subset=["date_added"])
    iso = df["date_added"].dt.isocalendar()
    df["year"] = iso["year"].astype(int)
    df["week"] = iso["week"].astype(int)
    df["date"] = df["date_added"].dt.date
    df["campaign"] = [classify_campaign(n, c)
                       for n, c in zip(df["coupon_name"].fillna(""),
                                       df["coupon_code"].fillna(""))]
    # Coerce numeric
    for col in ("quantity", "line_total_before_coupon", "line_total_after_coupon",
                 "line_coupon_discount_total", "coupon_discount_percent"):
        if col in df.columns:
            df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0)
    return df


def _baseline_qty(sales: pd.DataFrame, sku: str, start_yw: int,
                    pre_weeks: int = 4) -> float:
    """Average weekly qty_webshop for `sku` in the 4 weeks before
    `start_yw`. Returns 0 if no data."""
    if sales.empty:
        return 0.0
    s = sales[sales["sku"] == sku].copy()
    if s.empty:
        return 0.0
    s["yw"] = s["year"].astype(int) * 100 + s["week"].astype(int)
    # 4 weeks immediately before — same yr / handle year-cross via yw arithmetic
    yr = start_yw // 100
    wk = start_yw % 100
    pre = []
    for k in range(1, pre_weeks + 1):
        w = wk - k
        y = yr
        if w <= 0:
            w += 52
            y -= 1
        pre.append(y * 100 + w)
        s_pre = s[s["yw"].isin(pre)]
    if s_pre.empty:
        return 0.0
    qty = float(s_pre.get("qty_webshop", 0).fillna(0).sum())
    return qty / max(1, len(pre))


@st.cache_data(ttl=3600)
def build_marketing_campaigns(_coupons: pd.DataFrame, _sales: pd.DataFrame,
                                _name_map: dict, _cat_map: dict, _tier_map: dict):
    """Aggregate P-type coupons into Marketing campaigns.

    Returns: list of dicts with campaign metrics + per-SKU detail.
    F-type (individual/influencer/compensation/gift-card) codes are
    handled separately via `permanent_codes()`."""
    if _coupons.empty:
        return []
    P = _coupons[_coupons["coupon_type"] == "P"]
    if P.empty:
        return []

    out = []
    for camp, g in P.groupby("campaign"):
        first = g["date_added"].min()
        last = g["date_added"].max()
        days = (last.date() - first.date()).days + 1
        # Per-SKU rows
        sku_rows = []
        for sku, gg in g.groupby("sku"):
            units = int(gg["quantity"].sum())
            orders = int(gg["order_id"].nunique())
            rev_before = float(gg["line_total_before_coupon"].sum())
            rev_after = float(gg["line_total_after_coupon"].sum())
            discount = float(gg["line_coupon_discount_total"].sum())
            disc_pct = (discount / rev_before * 100) if rev_before > 0 else 0
            # uplift vs 4w pre-campaign webshop baseline
            start_yw = int(gg["year"].min()) * 100 + int(gg["week"].min())
            base = _baseline_qty(_sales, sku, start_yw, pre_weeks=4)
            # Approx "avg promo / week" while active
            n_weeks = max(1, gg.groupby(["year", "week"]).ngroups)
            avg_promo = units / n_weeks
            upl = (avg_promo / base) if base > 0 else 0
            sku_rows.append({
                "sku": sku,
                "name": (_name_map.get(sku, "") or "")[:60],
                "cat": _cat_map.get(sku, "") or "",
                "tier": _tier_map.get(sku, "") or "",
                "units": units,
                "orders": orders,
                "rev_before": rev_before,
                "rev_after": rev_after,
                "discount": discount,
                "disc_pct": round(disc_pct, 1),
                "avg_base": round(base, 1),
                "avg_promo": round(avg_promo, 1),
                "uplift": round(upl, 2),
            })
        sku_rows.sort(key=lambda r: r["units"], reverse=True)

        # Per-coupon list inside the campaign
        coupon_rows = []
        for code, gg in g.groupby("coupon_code"):
            coupon_rows.append({
                "code": code,
                "name": gg["coupon_name"].iloc[0],
                "first": gg["date_added"].min().date(),
                "last": gg["date_added"].max().date(),
                "days_active": (gg["date_added"].max().date()
                                  - gg["date_added"].min().date()).days + 1,
                "orders": int(gg["order_id"].nunique()),
                "units": int(gg["quantity"].sum()),
                "discount": float(gg["line_coupon_discount_total"].sum()),
                "avg_disc_pct": round(float(gg["coupon_discount_percent"].mean()), 1),
            })
        coupon_rows.sort(key=lambda r: r["units"], reverse=True)

        ups = [r["uplift"] for r in sku_rows if r["uplift"] > 0]
        out.append({
            "id": f"{camp}-{first.date()}",
            "campaign": camp,
            "first": first,
            "last": last,
            "days": days,
            "n_skus": len(sku_rows),
            "n_coupons": len(coupon_rows),
            "n_orders": int(g["order_id"].nunique()),
            "total_units": int(g["quantity"].sum()),
            "total_rev_before": float(g["line_total_before_coupon"].sum()),
            "total_rev_after": float(g["line_total_after_coupon"].sum()),
            "total_discount": float(g["line_coupon_discount_total"].sum()),
            "avg_uplift": round(sum(ups) / len(ups), 2) if ups else 0,
            "skus": sku_rows,
            "coupons": coupon_rows,
        })
    out.sort(key=lambda c: c["first"], reverse=True)
    return out


@st.cache_data(ttl=3600)
def permanent_codes(_coupons: pd.DataFrame):
    """F-type codes: influencer compensations, podcast deals, gift cards,
    long-running personal codes. Returns one row per coupon."""
    if _coupons.empty:
        return []
    F = _coupons[_coupons["coupon_type"] == "F"]
    if F.empty:
        return []
    rows = []
    for code, g in F.groupby("coupon_code"):
        rows.append({
            "code": code,
            "name": g["coupon_name"].iloc[0],
            "first": g["date_added"].min().date(),
            "last": g["date_added"].max().date(),
            "days_active": (g["date_added"].max().date()
                              - g["date_added"].min().date()).days + 1,
            "orders": int(g["order_id"].nunique()),
            "units": int(g["quantity"].sum()),
            "skus": int(g["sku"].nunique()),
            "discount": float(g["line_coupon_discount_total"].sum()),
            "rev_after": float(g["line_total_after_coupon"].sum()),
        })
    rows.sort(key=lambda r: r["discount"], reverse=True)
    return rows


def daily_units_for_campaign(_coupons: pd.DataFrame, campaign: str) -> pd.DataFrame:
    """Daily units (+ which coupon led that day) for a given campaign."""
    g = _coupons[(_coupons["campaign"] == campaign)
                  & (_coupons["coupon_type"] == "P")]
    if g.empty:
        return pd.DataFrame()
    daily = (g.groupby(["date", "coupon_code"])
               .agg(units=("quantity", "sum"),
                    orders=("order_id", "nunique"))
               .reset_index())
    return daily
