#!/usr/bin/env python3
"""
bidfood_to_json.py
Converteert het meest recente Bidfood JSON naar FM-klaar JSON.
Cron: 45 5 * * * python3 /var/www/html/bidfood/downloads/bidfood_to_json.py >> /var/log/bidfood_convert.log 2>&1
"""

import json, gzip, logging, os, re, shutil, sys, time
from datetime import datetime, timezone

INPUT_PATH  = "/var/www/html/bidfood/downloads/latest.json"
OUTPUT_PATH = "/var/www/html/bidfood/downloads/latest_fm.json"
LOG_PATH    = "/var/log/bidfood_convert.log"

CATEGORY_MAP = {
    "030-FR:Sauces chaudes prête à l'emploi-NL:Klant-en-klare hete sauzen":("32.5","36.2"),
    "Aardappelproducten":("32.5","36.2"),
    "Antipasti en gegrilde groente":("32.5","36.2"),
    "Aperitiefkoeken":("32.5","36.2"),
    "Aromatische kruiden en andere":("32.5","36.2"),
    "Azijn":("32.5","36.2"),
    "Bindmiddelen en verdikkingsmiddelen":("32.5","36.2"),
    "Bloem":("32.5","36.2"),
    "Bouillons en bases voor bouillons":("32.5","36.2"),
    "Chips":("32.5","36.2"),
    "Chocolade":("32.5","36.2"),
    "Coating hocolade en décoraties":("32.5","36.2"),
    "Coatinghocolade en décoraties":("32.5","36.2"),
    "Confituur":("32.5","36.2"),
    "Dressing en vinaigrette":("32.5","36.2"),
    "Fonds, fumets en jus":("32.5","36.2"),
    "Gedehydrateerde bases en soepen":("32.5","36.2"),
    "Gedroogde groenten":("32.5","36.2"),
    "Gedroogde hete sauzen":("32.5","36.2"),
    "Gedroogde vruchten en gekonfijte vruchte":("32.5","36.2"),
    "Gedroogde vruchten, noten en zaden":("32.5","36.2"),
    "Gemengde bereidingen":("32.5","36.2"),
    "Gist":("32.5","36.2"),
    "Granen":("32.5","36.2"),
    "Groenteconserven":("32.5","36.11"),
    "Groenten conserven":("32.5","36.11"),
    "Groentconserven":("32.5","36.11"),
    "Honing":("32.5","36.2"),
    "Ijsblokje":("32.1","36.5"),
    "Koekjes en zandkoekjes":("32.5","36.2"),
    "Konfijten, tapenades en pesto":("32.5","36.2"),
    "Korst":("32.4","36.6"),
    "Koude sauzen":("32.5","36.2"),
    "Kruiden":("32.5","36.2"),
    "Marinade":("32.5","36.2"),
    "Olië":("32.5","36.2"),
    "Ontbijtgranen":("32.5","36.2"),
    "Pasta":("32.5","36.2"),
    "Pinda's en pistachenoten":("32.5","36.2"),
    "Purees":("32.5","36.2"),
    "Rijst":("32.5","36.2"),
    "Roux":("32.5","36.2"),
    "Smeerpasta":("32.5","36.2"),
    "Snack mix":("32.5","36.2"),
    "Snoep":("32.5","36.2"),
    "Specerij":("32.5","36.2"),
    "Suiker en zoetstof":("32.5","36.2"),
    "Toastjes, broodstengels en blini's":("32.5","36.2"),
    "Toppings & Sauzen":("32.5","36.2"),
    "Vet":("32.5","36.2"),
    "Visblikjes":("32.5","36.17"),
    "Vruchten conserven":("32.5","36.2"),
    "Vruchtenbereidingen":("32.5","36.2"),
    "Wereldkruiden":("32.5","36.2"),
    "Zout":("32.5","36.2"),
    "Alternatief voor melkproducten":("32.3","36.19"),
    "Alternatief voor vlees":("32.3","36.8"),
    "Alternatief voor vleeswaren":("32.3","36.7"),
    "Bereide salade":("32.3","36.16"),
    "Blauwaderkazen":("32.3","36.19"),
    "Boter":("32.3","36.19"),
    "Brood":("32.3","36.6"),
    "Bun, hotdogbroodjes en wraps":("32.3","36.6"),
    "Eieren":("32.3","36.19"),
    "Gemarineerde vis":("32.3","36.17"),
    "Geperste kazen":("32.3","36.19"),
    "Geraspte kazen":("32.3","36.19"),
    "Ham gekookt":("32.3","36.7"),
    "Ham gerookt":("32.3","36.7"),
    "Hapje en bodem":("32.3","36.16"),
    "Harde kazen":("32.3","36.19"),
    "Heel":("32.3","36.8"),
    "Kaassnack":("32.3","36.19"),
    "Kip":("32.3","36.23"),
    "Melk":("32.3","36.19"),
    "Room":("32.3","36.19"),
    "Rug en filet":("32.3","36.17"),
    "Rundvlees":("32.3","36.8"),
    "Saladespreads":("32.3","36.16"),
    "Schaaldieren":("32.3","36.10"),
    "Smeltkazen":("32.3","36.19"),
    "Spek en speklapje":("32.3","36.7"),
    "Varkensvlees":("32.3","36.8"),
    "Verse kazen":("32.3","36.19"),
    "Yoghurt natuur":("32.3","36.19"),
    "Alternatief voor desserts":("32.4","36.16"),
    "Bak en schaal":("32.4","36.16"),
    "Cake en moelleux":("32.4","36.16"),
    "Chocoladebroodje":("32.4","36.6"),
    "Croissant":("32.4","36.6"),
    "Deegstuk":("32.4","36.6"),
    "Diepvries groenten":("32.4","36.11"),
    "Diepvries vruchten":("32.4","36.11"),
    "Diepvriesdesserts":("32.4","36.16"),
    "Frieten":("32.4","36.11"),
    "Gevogelte snack":("32.4","36.23"),
    "Halve stokbroden":("32.4","36.6"),
    "Individuele desserts":("32.4","36.16"),
    "Lasagne en gevulde pasta":("32.4","36.16"),
    "Minisdesserts":("32.4","36.16"),
    "Piccolo, sandiwch, speciale broden":("32.4","36.6"),
    "Pizza's, quiches en hartige taartjes":("32.4","36.16"),
    "Vissnack":("32.4","36.17"),
    "Vleessnack":("32.4","36.8"),
    "Wafels":("32.4","36.6"),
    "Zuiveldesserts":("32.4","36.16"),
    "Aperitieven en sterke dranken":("32.1","36.3"),
    "Sap en nectar":("32.1","36.5"),
    "Sirop":("32.1","36.5"),
    "Thee":("32.1","36.4"),
    "Warme dranken":("32.1","36.4"),
    "Wijn":("32.1","36.22"),
    "Dienbladen en presentatie":("32.2","36.14"),
    "Drogisterij":("32.6","36.15"),
    "Facility and administration":("32.6","36.15"),
    "Feesten en activiteiten":("32.6","36.15"),
    "Folie, aluminiumfolie en andere":("32.6","36.15"),
    "Kaars":("32.6","36.15"),
    "Klein materiaal":("32.6","36.15"),
    "Kookmateriaal":("32.6","36.15"),
    "Lichaamshygiëne":("32.6","36.15"),
    "Pizzamaterialen":("32.6","36.15"),
    "Ronde schalen, potten en kommen":("32.2","36.14"),
    "Schalen en bakjes":("32.2","36.14"),
    "Servies":("32.2","36.14"),
    "Tassen en zakjes":("32.6","36.15"),
    "Veiligheid":("32.6","36.15"),
    "Zaalmaterialen":("32.6","36.15"),
    "-FR:Sauces chaudes prête à l'emploi-NL:Klant-en-klare hete sauzen":("32.5","36.2"),
    "-FR:Chapelure-NL:Broodkruimel":("32.5","36.2"),
    "-FR:Gratins et pommes de terre préparée-NL:Gratins en bereiden aardappelen":("32.4","36.11"),
    "Andere":("",""),
    "Assortiment":("",""),
    "Oosterse snacks":("",""),
    "Verwerkte producten":("",""),
}

UNIT_MAP = {
    "PCE":"2.13","PAQ":"2.4","SAC":"2.16","BOI":"2.22",
    "BT":"2.10","TRA":"2.29","CRN":"2.2","SEA":"2.18",
}

logging.basicConfig(filename=LOG_PATH, level=logging.INFO,
    format="%(asctime)s  %(levelname)s  %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
log = logging.getLogger(__name__)

def parse_bilingual(s, lang="NL"):
    if not s: return ""
    m = re.search(rf"-{lang}:([^-]*)(?:-[A-Z]{{2}}:|$)", s)
    return m.group(1).strip() if m else s

def convert():
    t0 = time.time()
    log.info("=== Bidfood conversie gestart ===")
    if not os.path.exists(INPUT_PATH):
        log.error(f"Input niet gevonden: {INPUT_PATH}"); sys.exit(1)
    with open(INPUT_PATH, encoding="utf-8") as f:
        data = json.load(f)
    products = data.get("BidfoodCatalogs", [])
    total = len(products)
    log.info(f"Producten: {total}")
    articles = []
    unmapped = {}
    stats = {"volledig":0,"enkel_groep":0,"geen":0}
    for p in products:
        gi = p.get("generalInformation",[{}])[0]
        li = p.get("logisticInformation",[{}])[0]
        pr = p.get("prices",[{}])[0]
        wh = p.get("webHierarchy",[{}])[0] if p.get("webHierarchy") else {}
        erp          = gi.get("erpNumber","")
        name_nl      = gi.get("nameNL","")
        name_fr      = gi.get("nameFR","") or name_nl
        brand        = gi.get("brand","")
        availability = gi.get("availability","")
        temperature  = parse_bilingual(gi.get("temperature",""),"NL")
        min_unit     = li.get("minOrderUnit","")
        min_unit_syr = UNIT_MAP.get(min_unit,"")
        min_unit_nl  = li.get("minOrderUnitDescriptionNL","")
        min_unit_ean = li.get("minOrderUnitEAN","")
        max_unit     = li.get("maxOrderUnit","")
        max_unit_ean = li.get("maxOrderUnitEAN","")
        coefficient  = li.get("coefficientUnitMinAndMax","")
        net_weight   = li.get("netWeightAchat","")
        price        = pr.get("price","")
        vat          = pr.get("VAT","")
        family_raw   = wh.get("family","")
        subfam_raw   = wh.get("subFamily","")
        family_nl    = parse_bilingual(family_raw,"NL")
        family_fr    = parse_bilingual(family_raw,"FR")
        subfamily_nl = parse_bilingual(subfam_raw,"NL")
        subfamily_fr = parse_bilingual(subfam_raw,"FR")
        syr_groep, syr_subgroep = CATEGORY_MAP.get(family_nl,("",""))
        if not syr_groep and family_nl:
            unmapped[family_nl] = unmapped.get(family_nl,0)+1
        if syr_groep and syr_subgroep: stats["volledig"] += 1
        elif syr_groep:                stats["enkel_groep"] += 1
        else:                          stats["geen"] += 1
        articles.append({
            "erpNumber":erp,"nameNL":name_nl,"nameFR":name_fr,"brand":brand,
            "availability":availability,"isActive":1 if availability=="Available" else 0,
            "temperature":temperature,
            "minOrderUnit":min_unit,"minOrderUnitSYR":min_unit_syr,
            "minOrderUnitNL":min_unit_nl,"minOrderUnitEAN":min_unit_ean,
            "maxOrderUnit":max_unit,"maxOrderUnitEAN":max_unit_ean,
            "coefficient":coefficient,"netWeight":net_weight,
            "price":price,"VAT":vat,
            "familyNL":family_nl,"familyFR":family_fr,
            "subFamilyNL":subfamily_nl,"subFamilyFR":subfamily_fr,
            "syr_groep":syr_groep,"syr_subgroep":syr_subgroep,
        })
    output = {
        "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "source_file":  os.path.basename(os.path.realpath(INPUT_PATH)),
        "total": total, "articles": articles,
    }
    tmp = OUTPUT_PATH + ".tmp"
    with open(tmp,"w",encoding="utf-8") as f:
        json.dump(output,f,ensure_ascii=False,separators=(",",":"))
    os.replace(tmp, OUTPUT_PATH)
    gz_tmp = OUTPUT_PATH + ".gz.tmp"
    with open(OUTPUT_PATH,"rb") as fi:
        with gzip.open(gz_tmp,"wb") as fo:
            shutil.copyfileobj(fi,fo)
    os.replace(gz_tmp, OUTPUT_PATH+".gz")
    elapsed = time.time()-t0
    log.info(f"Klaar in {elapsed:.1f}s | {stats['volledig']} volledig / {stats['enkel_groep']} enkel groep / {stats['geen']} geen")
    if unmapped:
        for fam,cnt in sorted(unmapped.items(),key=lambda x:-x[1]):
            log.warning(f"  {cnt:3d}x niet gemapt: {fam}")
    print(f"OK: {total} artikelen → {OUTPUT_PATH} ({elapsed:.1f}s)")
    print(f"    {stats['volledig']} volledig / {stats['enkel_groep']} enkel groep / {stats['geen']} geen")

if __name__ == "__main__":
    convert()
