"""Backfill PromoCalendar/data/promo_calendar.csv from real ERP data.

Reads ../data/erp_promo_calendar.csv (ground-truth promo signals from Gath),
filters to entries from 2026-04-01 onwards (~CW14/2026), groups them into
"campaigns" by promo_types value + contiguous ISO-week range, and writes
one row per (campaign, contiguous-range) to promo_calendar.csv.

Schema mapping (ERP -> promo_calendar.csv):
  promo_types          -> name
  contiguous CW range  -> start_year/start_week/end_year/end_week
  SKUs in that range   -> skus (comma-separated)
  derived from SKU set -> category (most common grupacija)
  hardcoded            -> source = "B2B — FMCG" (FMCG is the closest match
                                                  for ERP wholesale-led promos;
                                                  user can edit per-promo)
  derived from window  -> status (live / done / approved)
  fixed                -> type = "Kampanja"

Safety:
  - Backs up the existing promo_calendar.csv to promo_calendar.csv.bak_<ts>
    before overwriting.
  - Rows with empty/null promo_types are dropped.
  - Semicolon-joined promo_types are split (e.g. "AKCIJE 04/26; GYMSTICK/TRBD")
    so a SKU in both campaigns ends up in both promo events.

Usage:
    python backfill_from_erp.py            # backfill, overwrites store
    python backfill_from_erp.py --append   # keep existing rows, append new
"""
from __future__ import annotations

import io
import sys
import shutil
from collections import defaultdict
from datetime import datetime, timedelta
from pathlib import Path
from uuid import uuid4

import pandas as pd

if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
    try:
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
    except Exception:
        pass

HERE = Path(__file__).parent
PROJECT_DATA = HERE.parent / "data"
PROMO_FILE = HERE / "data" / "promo_calendar.csv"

ERP_PATH = PROJECT_DATA / "erp_promo_calendar.csv"
SUBCAT_PATH = PROJECT_DATA / "sku_subcat_map.csv"

# Backfill window — user wants 2026-04-01 onwards = ISO CW14/2026
WINDOW_FROM_YW = 2026 * 100 + 14
NOW = datetime.now()
CUR_ISO = NOW.isocalendar()
CUR_YW = int(CUR_ISO[0]) * 100 + int(CUR_ISO[1])

# Multi-language grupacija normalization (mirror of update_sales.CAT_MAP)
GRUP_CAT_MAP = {
    "OBLAČILA IN OBUTEV": "ODJEĆA I OBUĆA",
    "BEKLEIDUNG UND SCHUHE": "ODJEĆA I OBUĆA",
    "KAMPFSPORT AUSRÜSTUNG": "BORILAČKA OPREMA",
    "BORILNA OPREMA": "BORILAČKA OPREMA",
    "SPORTNAHRUNG": "SPORTSKA PREHRANA",
    "ŠPORTNA PREHRANA": "SPORTSKA PREHRANA",
    "BIO IN SUPERFOODS": "BIO I SUPERFOODS",
    "BIO UND SUPERFOODS": "BIO I SUPERFOODS",
    "PROTEINE": "PROTEINI",
    "DRINKWARE IN DOM": "DRINKWARE I HOME",
    "DRINKWARE UND HOME": "DRINKWARE I HOME",
    "FITNESS EQUIPMENT": "FITNESS OPREMA",
    "GADGETS": "GADGETI",
    "USLUGE": "OTHER", "STORITVE": "OTHER",
    "DIENSTLEISTUNGEN": "OTHER", "PARIS": "OTHER",
}


def _week_to_monday(y: int, w: int) -> datetime:
    return datetime.strptime(f"{y}-W{w:02d}-1", "%G-W%V-%u")


def _contiguous_runs(week_pairs):
    """Given a sorted unique list of (year, week), return list-of-lists where
    each inner list is a contiguous 7-day-apart run."""
    if not week_pairs:
        return []
    runs = [[week_pairs[0]]]
    prev = _week_to_monday(*week_pairs[0])
    for yw in week_pairs[1:]:
        d = _week_to_monday(*yw)
        if (d - prev).days == 7:
            runs[-1].append(yw)
        else:
            runs.append([yw])
        prev = d
    return runs


def _status_for(start_yw: int, end_yw: int) -> str:
    """Pick a sensible status given where the promo sits relative to today."""
    if end_yw < CUR_YW:
        return "✓ done"
    if start_yw > CUR_YW:
        return "✅ approved"
    return "🟢 live"


def _outcome_for(name: str) -> str:
    n = (name or "").upper()
    if "OUTLET" in n:
        return "📦 Rješavanje lagera"
    return ""


def _source_for(name: str) -> str:
    """Heuristic: AKCIJE / HIT TJEDNA / OUTLET → MP retail; WS prefix → wholesale.
    User can re-tag per-row in the UI."""
    n = (name or "").upper()
    if n.startswith("WS ") or n.startswith("WS-"):
        return "B2B — FMCG"
    return "B2C — MP (retail)"


def main():
    append_mode = "--append" in sys.argv
    if not ERP_PATH.exists():
        print(f"ERROR: {ERP_PATH} not found")
        sys.exit(1)

    print(f"Reading {ERP_PATH}...")
    erp = pd.read_csv(ERP_PATH)
    erp = erp[erp.get("is_erp_promo", 1) == 1]
    erp["sku"] = erp["sku"].astype(str)
    erp["year"] = erp["year"].astype(int)
    erp["week"] = erp["week"].astype(int)
    erp["yw"] = erp["year"] * 100 + erp["week"]
    erp = erp[erp["yw"] >= WINDOW_FROM_YW].copy()
    print(f"  {len(erp):,} ERP rows from CW{WINDOW_FROM_YW % 100}/{WINDOW_FROM_YW // 100} onwards")

    if "promo_types" not in erp.columns:
        print("ERROR: promo_types column missing in ERP file")
        sys.exit(1)
    erp["promo_types"] = erp["promo_types"].fillna("").astype(str)

    # Split semicolon-joined promo_types so SKUs end up in every campaign
    # they were marked under. Each output row has exactly one campaign.
    exploded_rows = []
    for _, r in erp.iterrows():
        names = [p.strip() for p in str(r["promo_types"]).split(";") if p.strip()]
        for nm in names:
            exploded_rows.append({"sku": r["sku"], "year": r["year"],
                                   "week": r["week"], "campaign": nm})
    if not exploded_rows:
        print("Nothing to backfill — no promo_types values in window.")
        return
    ex = pd.DataFrame(exploded_rows)
    print(f"  Exploded into {len(ex):,} (sku, week, campaign) rows · "
          f"{ex['campaign'].nunique()} distinct campaigns")

    # SKU -> grupacija (canonical) for picking the dominant category
    sku_to_grup: dict[str, str] = {}
    if SUBCAT_PATH.exists():
        sm = pd.read_csv(SUBCAT_PATH)
        if "sku" in sm.columns and "grup" in sm.columns:
            for s, g in zip(sm["sku"].astype(str), sm["grup"].astype(str)):
                s = s.strip()
                g = g.strip()
                if s and g:
                    sku_to_grup[s] = GRUP_CAT_MAP.get(g, g)

    # Build one promo row per (campaign, contiguous-week-run)
    out_rows = []
    for campaign, grp in ex.groupby("campaign"):
        week_pairs = sorted({(int(y), int(w)) for y, w in zip(grp["year"], grp["week"])})
        for run in _contiguous_runs(week_pairs):
            start_y, start_w = run[0]
            end_y, end_w = run[-1]
            start_yw_i = start_y * 100 + start_w
            end_yw_i = end_y * 100 + end_w
            sub = grp[(grp["year"] * 100 + grp["week"] >= start_yw_i)
                       & (grp["year"] * 100 + grp["week"] <= end_yw_i)]
            skus = sorted({str(s).strip() for s in sub["sku"].unique() if str(s).strip()})
            # Dominant grupacija (most common across SKUs in the run)
            grup_counts: dict[str, int] = defaultdict(int)
            for s in skus:
                g = sku_to_grup.get(s)
                if g:
                    grup_counts[g] += 1
            cat = max(grup_counts, key=grup_counts.get) if grup_counts else ""

            ts = NOW.strftime("%Y-%m-%d %H:%M")
            out_rows.append({
                "id": uuid4().hex[:8],
                "name": campaign,
                "source": _source_for(campaign),
                "type": "Kampanja",
                "outcome": _outcome_for(campaign),
                "status": _status_for(start_yw_i, end_yw_i),
                "start_year": int(start_y),
                "start_week": int(start_w),
                "end_year": int(end_y),
                "end_week": int(end_w),
                "skus": ",".join(skus),
                "units": 0,
                "category": cat,
                "owner": "",
                "notes": f"Backfilled from erp_promo_calendar.csv on {ts}",
                "approval_log": f"[{ts}] backfill · created from ERP",
                "acknowledged_conflicts": "",
                "created_at": ts,
                "updated_at": ts,
            })

    new_df = pd.DataFrame(out_rows)
    print(f"\n  Generated {len(new_df)} promo events (campaign × contiguous-run)")

    # Merge with existing if append mode, else overwrite
    PROMO_FILE.parent.mkdir(parents=True, exist_ok=True)
    if PROMO_FILE.exists():
        ts_bak = NOW.strftime("%Y%m%d_%H%M%S")
        bak = PROMO_FILE.with_suffix(f".csv.bak_{ts_bak}")
        shutil.copy2(PROMO_FILE, bak)
        print(f"  Backed up existing store to {bak.name}")
        if append_mode:
            old = pd.read_csv(PROMO_FILE)
            new_df = pd.concat([old, new_df], ignore_index=True)
            print(f"  --append mode: merged with {len(old)} existing rows -> {len(new_df)} total")

    new_df.to_csv(PROMO_FILE, index=False, encoding="utf-8")
    print(f"\n  Written {PROMO_FILE} ({len(new_df)} rows)")
    print("  Distinct campaigns:")
    for nm, n in new_df["name"].value_counts().head(20).items():
        cats = new_df.loc[new_df["name"] == nm, "category"].iloc[0] or "—"
        print(f"    {n:>3}× {nm}  · top cat: {cats}")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"\nERROR: {e}")
        import traceback
        traceback.print_exc()
