"""
Polleo Demand — Demo data generator.

Produces a fully self-contained set of dummy CSVs in ./data/ so the app
can be installed and run without touching any real Polleo data.

50 fictional SKUs across 4 categories, 26 weeks of sales history, fake
costs, promo calendar, KAM/CM inputs, stock and incoming supply.

Run from inside the Demo folder:

    python generate_demo_data.py

Re-running overwrites everything in ./data/.
"""
from __future__ import annotations

import json
import math
import os
import random
from datetime import datetime, timedelta
from pathlib import Path

import numpy as np
import pandas as pd

random.seed(42)
np.random.seed(42)

OUT = Path(__file__).parent / "data"
OUT.mkdir(parents=True, exist_ok=True)
CONS = OUT / "consensus"
CONS.mkdir(exist_ok=True)

# ---------------- SKU master ----------------
CATS = {
    "PROTEINI": [
        "DemoWhey Vanilla 1kg", "DemoWhey Choco 1kg", "DemoWhey Strawberry 1kg",
        "DemoIso 900g", "DemoCasein Night 800g", "DemoMass Gainer 2kg",
        "DemoVegan Pea 750g", "DemoEgg Protein 700g",
    ],
    "BIO I SUPERFOODS": [
        "DemoMaca Powder 200g", "DemoSpirulina Tabs 250", "DemoChlorella 300g",
        "DemoChia Seeds 500g", "DemoBeet Powder 250g", "DemoCacao Nibs 200g",
        "DemoMatcha Premium 100g", "DemoTurmeric Extract 60",
    ],
    "RTD & SNACKS": [
        "DemoBar Choco 60g", "DemoBar Peanut 60g", "DemoBar Cookie 60g",
        "DemoShake Vanilla 330ml", "DemoShake Banana 330ml", "DemoCrisps Salt 50g",
        "DemoNuts Mix 150g", "DemoEnergy Drink 250ml",
    ],
    "SPORTSKA PREHRANA": [
        "DemoCreatine Mono 500g", "DemoBCAA Lemon 400g", "DemoEAA Mango 350g",
        "DemoPreWorkout 300g", "DemoBeta Alanine 200g", "DemoL-Carnitine Liquid",
        "DemoZMA 90 caps", "DemoMulti Sport 60",
    ],
    "FITNESS OPREMA": [
        "DemoShaker 600ml", "DemoTowel Gym Black", "DemoGloves L",
        "DemoLifting Belt M", "DemoResistance Band Set", "DemoFoam Roller",
    ],
    "DRINKWARE I HOME": [
        "DemoBottle 1L Black", "DemoBottle 750ml Pink", "DemoMug Steel 350ml",
        "DemoLunchbox 1.2L",
    ],
    "GADGETI": [
        "DemoFitTracker Watch", "DemoSmart Scale", "DemoJump Rope LED",
        "DemoMassage Gun Mini",
    ],
    "BORILAČKA OPREMA": [
        "DemoBoxing Gloves 12oz", "DemoMouth Guard", "DemoShin Pads",
        "DemoHand Wraps 4m",
    ],
}

skus = []
i = 1
for cat, names in CATS.items():
    for name in names:
        sku_code = f"DEMO{i:04d}"
        skus.append({"sku": sku_code, "name": name, "cat": cat})
        i += 1
# Trim to exactly 50
skus = skus[:50]
print(f"Generated {len(skus)} SKU master entries.")

# Tier assignment: roughly 20% Gold, 40% Silver, 40% Bronze.
# Bigger volume SKUs go to Gold (the protein products dominate).
TIER_ORDER = ["01 GOLD"] * 10 + ["02 SILVER"] * 20 + ["03 BRONZE"] * 20
random.shuffle(TIER_ORDER)
# Force first 6 (proteins) into Gold for realism
for k, s in enumerate(skus):
    if k < 6:
        s["oznaka"] = "01 GOLD"
    elif k < 25:
        s["oznaka"] = "02 SILVER"
    else:
        s["oznaka"] = "03 BRONZE"

# Wholesale share — varies by category. Proteins / RTD heavily wholesale.
WS_SHARE_BY_CAT = {
    "PROTEINI": 0.65, "BIO I SUPERFOODS": 0.45, "RTD & SNACKS": 0.55,
    "SPORTSKA PREHRANA": 0.50, "FITNESS OPREMA": 0.20, "DRINKWARE I HOME": 0.10,
    "GADGETI": 0.15, "BORILAČKA OPREMA": 0.30,
}

for s in skus:
    s["ws_share"] = max(0.0, min(0.95,
                              WS_SHARE_BY_CAT.get(s["cat"], 0.4) + random.uniform(-0.10, 0.10)))
    # Baseline weekly volume — Gold > Silver > Bronze
    base = {"01 GOLD": 700, "02 SILVER": 180, "03 BRONZE": 35}[s["oznaka"]]
    s["base_weekly"] = int(base * random.uniform(0.7, 1.4))
    # Variability
    s["cv"] = round(random.uniform(0.25, 1.4), 2)
    # Cost price
    s["cost_price"] = round(random.uniform(2.5, 28.0), 2)
    # Selling price — markup 20-65%
    markup = random.uniform(1.20, 1.65)
    s["sell_price"] = round(s["cost_price"] * markup, 2)
    s["ruc"] = round(s["sell_price"] - s["cost_price"], 2)


# ---------------- Calendar (ISO weeks) ----------------
# Anchor: today's ISO week. We generate 26 weeks of history ending last week.
today = datetime.now()
iso_today = today.isocalendar()
cur_y, cur_w = iso_today[0], iso_today[1]


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


def iter_weeks_back(year: int, week: int, n: int):
    """Yield (year, week) starting from the week BEFORE (year, week), going n weeks back."""
    out = []
    d = iso_monday(year, week) - timedelta(days=7)
    for _ in range(n):
        iso = d.isocalendar()
        out.append((iso[0], iso[1]))
        d -= timedelta(days=7)
    return list(reversed(out))


HIST_WEEKS = iter_weeks_back(cur_y, cur_w, 26)
FUTURE_WEEKS = []
d = iso_monday(cur_y, cur_w) + timedelta(days=7)
for _ in range(13):
    iso = d.isocalendar()
    FUTURE_WEEKS.append((iso[0], iso[1]))
    d += timedelta(days=7)

print(f"History: {HIST_WEEKS[0]} .. {HIST_WEEKS[-1]}  ({len(HIST_WEEKS)} weeks)")
print(f"Future:  {FUTURE_WEEKS[0]} .. {FUTURE_WEEKS[-1]}  ({len(FUTURE_WEEKS)} weeks)")


# ---------------- Sales history ----------------
def gen_weekly_qty(s, t_idx: int, total_weeks: int) -> dict:
    """Return per-channel quantities for SKU s in week index t_idx."""
    base = s["base_weekly"]
    # Mild seasonality (summer dip / new-year lift).
    season = 1.0 + 0.20 * math.sin(2 * math.pi * t_idx / 26)
    noise = np.random.normal(1.0, s["cv"] * 0.35)
    noise = max(0.05, noise)
    promo = 1.0
    is_promo = 0
    if random.random() < 0.10:  # 10% weeks have promo
        promo = random.uniform(1.5, 2.6)
        is_promo = 1
    qty = max(0, int(base * season * noise * promo))
    if qty == 0 and random.random() < 0.40:
        qty = random.randint(1, 5)  # avoid too many zeros
    qr = int(qty * (1 - s["ws_share"]) * random.uniform(0.7, 0.95))
    qw_web = int(qty * (1 - s["ws_share"]) * random.uniform(0.05, 0.30))
    qws = max(0, qty - qr - qw_web)
    is_ws_spike = 0
    if random.random() < 0.05 and s["ws_share"] > 0.3:
        spike = random.randint(int(qty * 0.5), int(qty * 1.2))
        qws += spike
        is_ws_spike = 1
    return {
        "qty_retail": qr, "qty_webshop": qw_web, "qty_wholesale": qws,
        "qty_total": qr + qw_web + qws, "is_any_promo": is_promo,
        "is_retail_promo": is_promo, "is_webshop_promo": 0,
        "is_wholesale_spike": is_ws_spike,
        "retail_discount_pct": round(random.uniform(10, 35), 1) if is_promo else 0,
        "webshop_discount_pct": 0,
    }


sales_rows = []
erp_promo_rows = []
for t_idx, (y, w) in enumerate(HIST_WEEKS):
    for s in skus:
        q = gen_weekly_qty(s, t_idx, len(HIST_WEEKS))
        ppp_r = round(s["sell_price"] * (1 - q["retail_discount_pct"] / 100), 2) if q["qty_retail"] else 0
        ppp_w = round(s["sell_price"] * 1.05, 2) if q["qty_webshop"] else 0
        ruc_r = round(q["qty_retail"] * (ppp_r - s["cost_price"]), 2)
        ruc_w = round(q["qty_webshop"] * (ppp_w - s["cost_price"]), 2)
        # Wholesale price ≈ 70% of retail
        ppp_ws = s["sell_price"] * 0.70
        ruc_ws = round(q["qty_wholesale"] * (ppp_ws - s["cost_price"]), 2)
        sales_rows.append({
            "sku": s["sku"], "year": y, "week": w,
            "qty_retail": q["qty_retail"], "qty_webshop": q["qty_webshop"],
            "qty_wholesale": q["qty_wholesale"], "qty_total": q["qty_total"],
            "avg_ppp_retail": ppp_r, "normal_ppp_retail": s["sell_price"],
            "retail_discount_pct": q["retail_discount_pct"],
            "is_retail_promo": q["is_retail_promo"],
            "avg_ppp_webshop": ppp_w, "normal_ppp_webshop": s["sell_price"] * 1.05,
            "webshop_discount_pct": 0, "is_webshop_promo": 0,
            "is_wholesale_spike": q["is_wholesale_spike"],
            "is_any_promo": q["is_any_promo"],
            "promo_pct_volume": round(q["qty_retail"] / max(q["qty_total"], 1), 2) if q["is_any_promo"] else 0,
            "ruc_retail": ruc_r, "ruc_webshop": ruc_w, "ruc_wholesale": ruc_ws,
            "ruc_total": round(ruc_r + ruc_w + ruc_ws, 2),
        })
        if q["is_any_promo"]:
            erp_promo_rows.append({
                "sku": s["sku"], "year": y, "week": w,
                "promo_types": "TOP_PROMO_PRICE", "is_erp_promo": 1,
            })

pd.DataFrame(sales_rows).to_csv(OUT / "sales_clean.csv", index=False)
pd.DataFrame(erp_promo_rows).to_csv(OUT / "erp_promo_calendar.csv", index=False)
print(f"  sales_clean.csv:        {len(sales_rows):,} rows")
print(f"  erp_promo_calendar.csv: {len(erp_promo_rows):,} rows")


# ---------------- SKU master files ----------------
plan_rows = []
for s in skus:
    cv = round(s["cv"] * random.uniform(0.85, 1.15), 2)
    xyz_total = "X" if cv < 0.5 else ("Y" if cv < 1.0 else "Z")
    cv_ws = round(cv * random.uniform(1.0, 1.5), 2)
    xyz_ws = "X" if cv_ws < 0.5 else ("Y" if cv_ws < 1.0 else "Z")
    plan_rows.append({
        "sku": s["sku"], "name": s["name"], "cat": s["cat"], "oznaka": s["oznaka"],
        "vpc": s["cost_price"], "ws_xyz": xyz_ws, "ws_cv": cv_ws,
        "ws_nz_weeks": random.randint(15, 26), "total_xyz": xyz_total,
        "total_cv": cv, "total_nz_weeks": random.randint(20, 26),
        "ws_share_26w": round(s["ws_share"], 3),
    })
pd.DataFrame(plan_rows).to_csv(OUT / "sku_plan_list.csv", index=False)

pd.DataFrame([{"sku": s["sku"], "cost_price": s["cost_price"], "ruc": s["ruc"]}
              for s in skus]).to_csv(OUT / "sku_costs.csv", index=False)

prices_rows = []
agg_qty = (pd.DataFrame(sales_rows).groupby("sku")
           .agg(qty_retail=("qty_retail", "sum"),
                qty_webshop=("qty_webshop", "sum"),
                qty_wholesale=("qty_wholesale", "sum"))
           .reset_index())
qty_lookup = {r.sku: r for _, r in agg_qty.iterrows()}
for s in skus:
    q = qty_lookup.get(s["sku"])
    prices_rows.append({
        "sku": s["sku"],
        "avg_sell_price": s["sell_price"],
        "normal_retail_ppp": s["sell_price"],
        "normal_webshop_ppp": round(s["sell_price"] * 1.05, 2),
        "qty_retail": int(q.qty_retail) if q is not None else 0,
        "qty_webshop": int(q.qty_webshop) if q is not None else 0,
        "qty_wholesale": int(q.qty_wholesale) if q is not None else 0,
        "weeks_active": 26,
    })
pd.DataFrame(prices_rows).to_csv(OUT / "sku_prices.csv", index=False)

pd.DataFrame([{"sku": s["sku"], "name": s["name"], "cat": s["cat"]}
              for s in skus]).to_csv(OUT / "sku_category_map.csv", index=False)

SUB_BY_CAT = {
    "PROTEINI": ["WHEY", "ISOLATE", "VEGAN"],
    "BIO I SUPERFOODS": ["POWDERS", "SEEDS", "EXTRACTS"],
    "RTD & SNACKS": ["BARS", "SHAKES", "DRY"],
    "SPORTSKA PREHRANA": ["AMINO", "PRE-INTRA", "VITAMINS"],
    "FITNESS OPREMA": ["GLOVES", "BELTS", "BANDS"],
    "DRINKWARE I HOME": ["BOTTLES", "MUGS", "CONTAINERS"],
    "GADGETI": ["WEARABLE", "SMART", "RECOVERY"],
    "BORILAČKA OPREMA": ["GLOVES", "PROTECTION"],
}
subcat_rows = []
for s in skus:
    sub = random.choice(SUB_BY_CAT.get(s["cat"], ["GENERAL"]))
    subcat_rows.append({"sku": s["sku"], "name": s["name"],
                         "sub_cat": f"{s['cat']} - {sub}",
                         "grup": s["cat"]})
pd.DataFrame(subcat_rows).to_csv(OUT / "sku_subcat_map.csv", index=False)

print("  sku master files written.")


# ---------------- Uplift CSVs ----------------
uplift_rows = []
for s in skus:
    promo_w = random.randint(1, 4)
    normal_w = 26 - promo_w
    avg_n = s["base_weekly"]
    avg_p = int(avg_n * random.uniform(1.5, 2.4))
    uplift_rows.append({
        "sku": s["sku"], "grupacija": s["cat"],
        "promo_weeks": promo_w, "normal_weeks": normal_w,
        "avg_normal": avg_n, "avg_promo": avg_p,
        "promo_uplift": round(avg_p / max(avg_n, 1), 2),
        "ws_spike_weeks": random.randint(0, 2),
        "ws_uplift": round(random.uniform(1.3, 2.2), 2),
    })
pd.DataFrame(uplift_rows).to_csv(OUT / "sku_uplift.csv", index=False)

cat_uplift = pd.DataFrame([{"grupacija": c, "cat_promo_uplift": round(random.uniform(1.30, 1.55), 2)}
                            for c in CATS.keys()])
cat_uplift.to_csv(OUT / "cat_uplift.csv", index=False)


# ---------------- VP / MP inputs ----------------
def gen_ontop_csv(prefix: str, n_skus: int, with_buyers: bool):
    """Build vp_input / mp_input with future CW columns + a per-KAM detail."""
    cw_labels = [f"CW{w}" for (_y, w) in FUTURE_WEEKS]
    chosen = random.sample(skus, n_skus)
    summed_rows = []
    detail_rows = []
    buyers_pool = ["Konzum", "Spar", "DM", "Kaufland"] if with_buyers else [""]
    for s in chosen:
        per_cw = {c: 0 for c in cw_labels}
        # Pick 1-3 weeks to commit on
        weeks_committed = random.sample(cw_labels, k=random.randint(1, 3))
        for c in weeks_committed:
            per_cw[c] = random.randint(50, 600) if prefix == "vp" else random.randint(20, 250)
        row = {"sku": s["sku"], **per_cw}
        summed_rows.append(row)
        # Detail
        kam = "Selma" if prefix == "vp" else "Ivan"
        buyer = random.choice(buyers_pool) if with_buyers else ""
        detail_rows.append({
            "sku": s["sku"], "type": "on-top demand",
            "kam": kam, "buyer": buyer, **per_cw,
        })
    pd.DataFrame(summed_rows).to_csv(OUT / f"{prefix}_input.csv", index=False)
    pd.DataFrame(detail_rows).to_csv(OUT / f"{prefix}_input_detail.csv", index=False)


gen_ontop_csv("vp", n_skus=20, with_buyers=True)
gen_ontop_csv("mp", n_skus=15, with_buyers=False)
print("  VP/MP input + detail CSVs written.")


# ---------------- Stock & supply ----------------
stock_rows = []
stores_rows = []
stores_at_rows = []
stores_slo_rows = []
incoming_rows = []
master_rows = []
SUPPLIERS = ["DemoSupplier A", "DemoSupplier B", "DemoSupplier C", "DemoSupplier D"]
for s in skus:
    base = s["base_weekly"]
    stock_rows.append({"sku": s["sku"], "on_hand": int(base * random.uniform(2.5, 8.0))})
    stores_rows.append({"sku": s["sku"], "on_hand": int(base * random.uniform(0.4, 2.0))})
    stores_at_rows.append({"sku": s["sku"], "on_hand": int(base * random.uniform(0.2, 1.0))})
    stores_slo_rows.append({"sku": s["sku"], "on_hand": int(base * random.uniform(0.2, 1.0))})
    if random.random() < 0.45:
        for _ in range(random.randint(1, 2)):
            (yi, wi) = random.choice(FUTURE_WEEKS[2:8])
            incoming_rows.append({
                "sku": s["sku"], "year": yi, "week": wi,
                "qty": int(base * random.uniform(2.0, 6.0)),
            })
    master_rows.append({
        "sku": s["sku"],
        "supplier": random.choice(SUPPLIERS),
        "lead_time_weeks": random.choice([4, 6, 8, 10, 12]),
        "moq": random.choice([100, 200, 300, 500, 1000]),
    })

pd.DataFrame(stock_rows).to_csv(OUT / "stock.csv", index=False)
pd.DataFrame(stores_rows).to_csv(OUT / "stock_stores.csv", index=False)
pd.DataFrame(stores_at_rows).to_csv(OUT / "stock_stores_at.csv", index=False)
pd.DataFrame(stores_slo_rows).to_csv(OUT / "stock_stores_slo.csv", index=False)
pd.DataFrame(incoming_rows).to_csv(OUT / "incoming_supply.csv", index=False)
pd.DataFrame(master_rows).to_csv(OUT / "supply_master.csv", index=False)
print("  Stock + supply CSVs written.")


# ---------------- KAM/CM config (anonymised) ----------------
kam_cfg = {
    "slack_config": {
        "bot_token": "",
        "channel_id": "",
        "nudge_day": "Monday",
        "nudge_hour": 9,
        "deadline_day": "Monday",
        "deadline_hour": 17,
    },
    "kam_cm_config": {
        "Demo_Selma": {
            "display_name": "Selma",
            "role": "VP",
            "type": "KAM",
            "channel": "wholesale",
            "categories": "ALL",
            "buyers": ["Konzum", "Spar", "DM", "Kaufland"],
            "slack_user_id": "",
        },
        "Demo_Ivan": {
            "display_name": "Ivan",
            "role": "MP",
            "type": "CM",
            "channel": "food retail",
            "categories": ["BIO I SUPERFOODS", "PROTEINI", "RTD & SNACKS", "SPORTSKA PREHRANA"],
            "slack_user_id": "",
        },
    },
    "excluded_categories_mp": ["ODJEĆA I OBUĆA"],
}
(OUT / "kam_cm_config.json").write_text(json.dumps(kam_cfg, indent=2, ensure_ascii=False),
                                         encoding="utf-8")


# ---------------- Backtest FA (synthetic past forecasts) ----------------
bt_rows = []
for (y, w) in HIST_WEEKS[-8:]:  # last 8 weeks
    for s in skus:
        wk_actual = next((r for r in sales_rows
                          if r["sku"] == s["sku"] and r["year"] == y and r["week"] == w), None)
        if not wk_actual:
            continue
        ac = wk_actual["qty_total"]
        # Forecast = actual ± noise
        noise = np.random.normal(1.0, 0.20)
        fc = max(0, int(ac * noise))
        ws_fc = int(fc * s["ws_share"])
        rt_fc = fc - ws_fc
        ws_ac = wk_actual["qty_wholesale"]
        rt_ac = wk_actual["qty_retail"] + wk_actual["qty_webshop"]
        bt_rows.append({
            "sku": s["sku"], "year": y, "week": w,
            "forecast": fc, "actual": ac,
            "forecast_retail": rt_fc, "forecast_wholesale": ws_fc,
            "actual_retail": rt_ac, "actual_wholesale": ws_ac,
            "channel_mode": "split" if s["ws_share"] >= 0.5 else "total",
            "ws_share": round(s["ws_share"], 3),
            "model": random.choice(["AutoARIMA", "AutoTheta", "CrostonOptimized", "GBR"]),
            "model_retail": "AutoTheta", "model_wholesale": "AutoARIMA",
            "cat": s["cat"], "oznaka": s["oznaka"],
        })
pd.DataFrame(bt_rows).to_csv(OUT / "backtest_fa.csv", index=False)
print(f"  backtest_fa.csv: {len(bt_rows):,} rows")


# ---------------- Forecast log + factor history (sparse but present) ----------------
log_rows = []
factor_rows = []
run_date_str = (today - timedelta(days=2)).strftime("%Y-%m-%d %H:%M")
for s in skus:
    base = s["base_weekly"]
    for j, (y, w) in enumerate(FUTURE_WEEKS):
        season = 1.0 + 0.20 * math.sin(2 * math.pi * (j + 26) / 26)
        fc = max(0, int(base * season * random.uniform(0.85, 1.15)))
        log_rows.append({
            "run_date": run_date_str,
            "run_year": cur_y, "run_week": cur_w,
            "target_year": y, "target_week": w,
            "sku": s["sku"], "forecast": fc,
        })
        factor_rows.append({
            "run_date": run_date_str,
            "run_year": cur_y, "run_week": cur_w,
            "target_year": y, "target_week": w,
            "sku": s["sku"], "factor": round(random.choice([1.0, 1.0, 1.0, 1.1, 0.9]), 2),
        })

pd.DataFrame(log_rows).to_csv(OUT / "forecast_log.csv", index=False)
pd.DataFrame(factor_rows).to_csv(OUT / "factor_history.csv", index=False)


# ---------------- Empty / placeholder files used by the app ----------------
(OUT / "config_toml.txt").write_text("# Demo config — leave empty\n", encoding="utf-8")

# Snapshot folder marker
(CONS / "_README.txt").write_text(
    "Consensus snapshots are written here every forecast run.\n"
    "Empty in the demo until you Run Forecast at least once.\n",
    encoding="utf-8",
)

print()
print("=" * 60)
print(f"DEMO DATA READY in {OUT}")
print("=" * 60)
print(f"  SKUs:        {len(skus)}  ({len(CATS)} categories)")
print(f"  Sales rows:  {len(sales_rows):,}  ({len(HIST_WEEKS)} weeks)")
print(f"  Backtest FA: {len(bt_rows):,}  rows ({min(8, len(HIST_WEEKS))} weeks)")
print(f"  VP / MP inputs: 20 / 15 SKUs (future horizon)")
print()
print("Next step: launch the app — run Start_Demo.bat (Windows)")
print("           or  bash start_demo.sh  (Mac / Linux).")
