#!/usr/bin/env python3
"""Build the LogShip import catalog for the lead "Kazaar Fragrances" from the storefront's own
GraphQL feed (https://kazaarfragrances.com/api/graphql, query `Products(collection_slug:
"alle-produkte")`, pages 1..3, saved as catalog-raw.json).

Output: catalog.json — one entry per sellable article for import-merchant-catalog.sh:
  { value (SKU), name, grossPrice, netPrice, upc, sku (shop variant sku), weight, length, width,
    height, group1 (brand), group2 (inspired-by), classification (kind), description, documentNote,
    imageUrl, stock }

Matrix requested for the demo: every fragrance (kind perfume / pheromone_perfume) as
30 ml and 50 ml × 20 % / 30 % / 50 % concentration = 6 articles. Prices come from the shop's real
variants; a combination the shop does not sell (30 ml / 50 %) is derived from the product's own
50 ml / 50 % price with the catalog-wide 30-vs-50 ml ratio. Oils (kind oil / pheromone_oil)
keep their real 3 / 5 / 8 ml variants. Car scents, refills, bundles, samples are skipped.

  python3 build-catalog.py catalog-raw.json catalog.json
"""
import html, json, re, statistics, sys

raw_path, out_path = sys.argv[1], sys.argv[2]
raw = json.load(open(raw_path, encoding="utf-8"))
BRAND = "Kazaar"
VAT = 1.19
SIZES = ["30", "50"]
CONCS = ["20", "30", "50"]
WEIGHT = {"30": 0.14, "50": 0.21}                # kg incl. glass bottle + box
DIMS = {"30": (10, 4, 4), "50": (12, 5, 5)}      # cm L × W × H
OIL_WEIGHT = {"3": 0.03, "5": 0.04, "8": 0.05}

def code(name):
    c = re.sub(r"[^A-Z0-9]", "", name.upper())
    return c[:12] or "X"

def strip_html(s):
    s = re.sub(r"<[^>]+>", " ", s or "")
    s = html.unescape(re.sub(r"\s+", " ", s)).strip()
    return s

def combo(v):
    m = re.search(r"(\d+)\s*ml\s*/\s*(\d+)\s*%", v.get("name") or "")
    return (m.group(1), m.group(2)) if m else None

def oil_ml(v):
    m = re.search(r"(\d+)\s*ml", v.get("name") or "")
    return m.group(1) if m else None

fr = [p for p in raw if p.get("kind") in ("perfume", "pheromone_perfume", "oil", "pheromone_oil")]

# catalog-wide median price per combination (fallbacks + the 30 ml / 50 % derivation)
med = {}
for p in fr:
    for v in p.get("variations") or []:
        c = combo(v)
        if c and v.get("price"):
            med.setdefault(c, []).append(float(v["price"]))
med = {k: statistics.median(vs) for k, vs in med.items()}
ratio_30_50 = med[("30", "30")] / med[("50", "30")]   # same concentration, 30 ml vs 50 ml

items = []
seen = set()
for p in fr:
    name = (p.get("name") or "").strip()
    vendor = (p.get("vendor") or {}).get("name") or ""
    kind = p.get("kind")
    img_default = ((p.get("first_image_urls") or {}).get("original")) or p.get("first_image_url")
    note = strip_html(p.get("description"))[:1900]
    pcode = code(name)
    if pcode in seen:   # two products sharing a code (e.g. "Nero" and "Nero Oil") – disambiguate
        pcode = (pcode[:9] + code(p.get("slug") or "")[-3:])
    seen.add(pcode)
    variants = {}
    for v in p.get("variations") or []:
        c = combo(v)
        if c: variants[c] = v
    if kind in ("perfume", "pheromone_perfume"):
        base_scale = None
        if ("30", "20") in variants and variants[("30", "20")].get("price"):
            base_scale = float(variants[("30", "20")]["price"]) / med[("30", "20")]
        for size in SIZES:
            for conc in CONCS:
                v = variants.get((size, conc))
                if v and v.get("price"):
                    gross = float(v["price"])
                elif (size, conc) == ("30", "50"):
                    # never sold by the shop: 50 ml / 50 % price (real or scaled median) × 30-vs-50 ml ratio
                    base50 = float(variants[("50", "50")]["price"]) if variants.get(("50", "50"), {}).get("price") \
                             else med[("50", "50")] * (base_scale or 1.0)
                    gross = round(base50 * ratio_30_50, 1)
                else:
                    gross = round(med.get((size, conc), 0) * (base_scale or 1.0), 1)
                gross = round(gross + 0.0, 2)
                img = ((v or {}).get("primaryImage") or {}).get("xlarge") if v else None
                if not img and conc == "50" and variants.get(("50", "50")):
                    img = ((variants[("50", "50")].get("primaryImage")) or {}).get("xlarge")
                img = img or img_default
                stock = (v or {}).get("stock")
                stock = min(int(stock), 200) if isinstance(stock, int) and stock > 0 else 25
                items.append({
                    "value": f"KZ-{pcode}-{size}-{conc}",
                    "name": f"{name} {size} ml / {conc}%",
                    "grossPrice": gross,
                    "netPrice": round(gross / VAT, 2),
                    "upc": (v or {}).get("barcode") or "",
                    "sku": (v or {}).get("sku") or "",
                    "weight": WEIGHT[size],
                    "length": DIMS[size][0], "width": DIMS[size][1], "height": DIMS[size][2],
                    "group1": BRAND,
                    "group2": vendor,
                    "classification": kind,
                    "description": f"{BRAND} Duftzwilling · inspiriert von {vendor} · {conc}% Parfümöl-Konzentration · {size} ml"[:255],
                    "documentNote": note,
                    "imageUrl": img,
                    "stock": stock,
                    "shopProduct": p.get("slug"),
                    "shopVariantId": (v or {}).get("id"),
                })
    else:  # oils: real 3/5/8 ml variants
        for v in p.get("variations") or []:
            ml = oil_ml(v)
            if not ml or not v.get("price"): continue
            gross = round(float(v["price"]), 2)
            stock = v.get("stock")
            stock = min(int(stock), 200) if isinstance(stock, int) and stock > 0 else 25
            items.append({
                "value": f"KZ-{pcode}-OIL-{ml}",
                "name": (f"{name} {ml} ml" if "Öl" in name else f"{name} Parfum Öl {ml} ml"),
                "grossPrice": gross, "netPrice": round(gross / VAT, 2),
                "upc": v.get("barcode") or "", "sku": v.get("sku") or "",
                "weight": OIL_WEIGHT.get(ml, 0.05), "length": 8, "width": 2.5, "height": 2.5,
                "group1": BRAND, "group2": vendor, "classification": kind,
                "description": f"{BRAND} Parfum Öl · inspiriert von {vendor} · {ml} ml Roll-on"[:255],
                "documentNote": note,
                "imageUrl": ((v.get("primaryImage") or {}).get("xlarge")) or img_default,
                "stock": stock, "shopProduct": p.get("slug"), "shopVariantId": v.get("id"),
            })

json.dump(items, open(out_path, "w", encoding="utf-8"), ensure_ascii=False, indent=0)
dup = len(items) - len({i["value"] for i in items})
print(f"{len(items)} articles from {len(fr)} products ({dup} duplicate SKUs) -> {out_path}")
print("price medians:", {f"{k[0]}ml/{k[1]}%": v for k, v in sorted(med.items())}, "30/50 ratio", round(ratio_30_50, 3))
