#!/usr/bin/env python3
"""
Fantasywelt.de Neuheiten-Tracker v9
====================================
Starten:
  python3 fantasywelt_neuheiten.py                  # Server auf Port 5544
  python3 fantasywelt_neuheiten.py --port 8080
  python3 fantasywelt_neuheiten.py --once            # Einmal ohne Server
"""

import csv, io, json, os, sys, urllib.request, urllib.error
from datetime import datetime, timedelta
from pathlib import Path
from html import escape
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

# ─── KONFIGURATION ───────────────────────────────────────────────────────────

CSV_URL = "https://www.fantasywelt.de/export/brettspiele.csv"
DATA_DIR = Path("/home/hermes/workspace/fantasywelt_data")
HISTORY_DIR = DATA_DIR / "history"
OUTPUT_FILE = DATA_DIR / "neuheiten.html"
SNAPSHOT_FILE = DATA_DIR / "last_snapshot.json"
CHANGES_FILE = DATA_DIR / "changes.json"
DEFAULT_PORT = 5544
DEFAULT_DAYS_NEW = 14
DEFAULT_DAYS_PREORDER = 7
AFFILIATE_TAG = "bsn=ig1UxQ0L2cNMNslzFhyr"

ZUBEHOER_KEYWORDS = [
    "schutzhüll","sleeve","hülle","hüllen","deckbox","deck box","deck-box",
    "schaumstoff","feldherr","portfolio","zipfolio","flexxfolio","play-mat",
    "playmat","play mat","toploader","card holder","card divider",
    "pages ","page ","pocket","kartenbox","card case","card box",
    "faltkarton","lagerbox","gaming case","gaming box",
    "pro-matte","pro-fit","ultra pro","ultra-snug",
    "dragon shield sleeves","dragon shield: sleeves","dragon shield schutzhüll",
    "board game sleeves","board games sleeves","kmc small","kmc standard",
    "precise-fit","soft sleeves",
    "9-pocket","4-pocket","8-pocket","12-pocket","16-pocket","18-pocket",
    "e-raptor spieler-tableau","organizer","würfel","dice tower",
    "raster schaumstoff","schaumstoffeinlage","schaumstoffzuschnitt",
    "schaumstoff-set","schaumstofeinlage","schaumstoff abdeckung",
    "half-size schaumstoff","full-size schaumstoff","tasche für","medium tasche",
]
EXCLUDE_KEYWORDS = ["mängelexemplar","mängelexemplare","mangelexemplar","%sale%"]

# ─── HILFSFUNKTIONEN ─────────────────────────────────────────────────────────

def should_skip(name):
    n = name.lower()
    return any(kw in n for kw in ZUBEHOER_KEYWORDS) or any(kw in n for kw in EXCLUDE_KEYWORDS)

def add_affiliate(url):
    if not url: return url
    return url + ("&" if "?" in url else "?") + AFFILIATE_TAG

def download_csv():
    req = urllib.request.Request(CSV_URL, headers={"User-Agent": "BSN-Tracker/9.0"})
    with urllib.request.urlopen(req, timeout=60) as resp:
        data = resp.read()
        try: return data.decode("utf-8")
        except UnicodeDecodeError: return data.decode("latin-1")

def parse_csv(raw):
    reader = csv.DictReader(io.StringIO(raw), delimiter=";", quotechar='"')
    products = []
    for row in reader:
        name = row.get("Name", "").strip()
        if not name: continue
        products.append({
            "name": name, "sku": row.get("Artikelnummer","").strip(),
            "ean": row.get("EAN","").strip(), "price": row.get("VK/Sonderpreis","0").strip(),
            "available": row.get("Verfügbarkeit","").strip(),
            "supply": row.get("Lieferstatus","").strip(),
            "created": row.get("Erstellt am","").strip(),
            "release": row.get("Erscheint am","").strip(),
            "url": row.get("Produkt-URL","").strip(),
            "image": row.get("Bild-URL","").strip(),
            "bestprice": row.get("Best-Price_Garantie!","N").strip(),
        })
    return products

def format_price(s):
    try: return f"{float(s.replace(',','.')):.2f}".replace(".",",")
    except: return s

def detect_language(name):
    n = name.upper()
    if "(DE|EN)" in n or "(DEEN)" in n or "(DE/EN)" in n: return "DE/EN"
    elif "(DE)" in n or "(DEUTSCH)" in n: return "DE"
    elif "(EN)" in n or "(ENGLISH)" in n or "(ENGLISCH)" in n: return "EN"
    elif "MULTILINGUAL" in n: return "Multi"
    return ""

# ─── SNAPSHOT & ÄNDERUNGSLOG ─────────────────────────────────────────────────

def load_snapshot():
    if not SNAPSHOT_FILE.exists(): return None
    try:
        with open(SNAPSHOT_FILE, "r", encoding="utf-8") as f: return json.load(f)
    except: return None

def save_snapshot(products, timestamp):
    DATA_DIR.mkdir(parents=True, exist_ok=True); HISTORY_DIR.mkdir(parents=True, exist_ok=True)
    snap = {"timestamp": timestamp, "products": {
        p["sku"]: {"name":p["name"],"available":p["available"],"price":p["price"],"supply":p["supply"]}
        for p in products if p["sku"]}}
    with open(SNAPSHOT_FILE, "w", encoding="utf-8") as f: json.dump(snap, f, ensure_ascii=False, indent=2)
    safe = timestamp.replace(":","-").replace(" ","_")
    with open(HISTORY_DIR / f"snapshot_{safe}.json", "w", encoding="utf-8") as f: json.dump(snap, f, ensure_ascii=False, indent=2)

def load_changes():
    if not CHANGES_FILE.exists(): return []
    try:
        with open(CHANGES_FILE, "r", encoding="utf-8") as f: return json.load(f)
    except: return []

def save_changes(changes):
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    with open(CHANGES_FILE, "w", encoding="utf-8") as f: json.dump(changes, f, ensure_ascii=False, indent=2)

def cleanup_changes(changes, keep_days=30):
    cutoff = (datetime.now() - timedelta(days=keep_days)).isoformat()
    return [c for c in changes if c.get("detected_at","") >= cutoff]

def cleanup_old_snapshots(keep_days=30):
    if not HISTORY_DIR.exists(): return
    cutoff = datetime.now() - timedelta(days=keep_days)
    for f in sorted(HISTORY_DIR.glob("snapshot_*.json")):
        try:
            with open(f) as fh: ts = json.load(fh).get("timestamp","")
            if datetime.strptime(ts[:19], "%Y-%m-%d %H:%M:%S") < cutoff: f.unlink()
        except: continue

# ─── NEUHEITEN-ERKENNUNG ────────────────────────────────────────────────────

def detect_changes(products, old_snapshot, timestamp):
    old_products = old_snapshot.get("products", {})
    nc = []
    for p in products:
        if should_skip(p["name"]): continue
        sku = p["sku"]
        if not sku: continue
        old = old_products.get(sku)
        ct = None
        if p["available"] == "auf Lager":
            if old is None:
                # Prüfe Erscheinungsdatum: Spiele älter als 120 Tage sind
                # keine echten Neuheiten, sondern nur neu im Fantasywelt-Katalog
                release_date = None
                if p.get("release"):
                    try: release_date = datetime.strptime(p["release"], "%Y-%m-%d")
                    except: pass
                if release_date and release_date < datetime.now() - timedelta(days=120):
                    ct = "wieder_im_lager"    # altes Spiel, neu im Katalog → wie Restock
                else:
                    ct = "neu_im_lager"       # wirklich neu (oder kein release date)
            elif old.get("available") != "auf Lager":
                ct = "wieder_im_lager"    # war schon im Katalog, jetzt wieder da
        if p["supply"] == "Vorbestellung":
            if old is None: ct = "neue_vorbestellung"
            elif old.get("supply") != "Vorbestellung": ct = "neue_vorbestellung"
        if ct: nc.append({"sku": sku, "change_type": ct, "detected_at": timestamp,
                          "name": p["name"], "price": p["price"],
                          "url": p.get("url",""), "image": p.get("image",""),
                          "release": p.get("release",""), "bestprice": p.get("bestprice","N")})
    return nc

def initial_seed(products, days_new, days_preorder, timestamp):
    cutoff_new = datetime.now() - timedelta(days=days_new)
    cutoff_pre = datetime.now() - timedelta(days=days_preorder)
    seed, seen = [], set()
    for p in products:
        if should_skip(p["name"]): continue
        sku = p["sku"]
        if not sku or sku in seen: continue
        try: created = datetime.strptime(p["created"], "%Y-%m-%d")
        except: continue
        if p["supply"] == "Vorbestellung" and created >= cutoff_pre:
            seen.add(sku); seed.append({"sku":sku,"change_type":"neue_vorbestellung","detected_at":timestamp,
                                       "name":p["name"],"price":p["price"],"url":p.get("url",""),
                                       "image":p.get("image",""),"release":p.get("release",""),
                                       "bestprice":p.get("bestprice","N")})
        elif p["available"] == "auf Lager" and created >= cutoff_new:
            seen.add(sku); seed.append({"sku":sku,"change_type":"neu_im_lager","detected_at":timestamp,
                                       "name":p["name"],"price":p["price"],"url":p.get("url",""),
                                       "image":p.get("image",""),"release":p.get("release",""),
                                       "bestprice":p.get("bestprice","N")})
    return seed

def build_neuheiten(products, changes, days_new, days_preorder):
    cutoff_new = (datetime.now() - timedelta(days=days_new)).isoformat()
    cutoff_pre = (datetime.now() - timedelta(days=days_preorder)).isoformat()
    pm = {p["sku"]: p for p in products if p["sku"]}
    neu_lager, wieder_lager, vorb = {}, {}, {}
    for c in changes:
        sku, ct, det = c["sku"], c["change_type"], c.get("detected_at","")
        if ct == "neu_im_lager" and det >= cutoff_new:
            if sku not in neu_lager or det > neu_lager[sku]["detected_at"]: neu_lager[sku] = c
        if ct == "wieder_im_lager" and det >= cutoff_new:
            if sku not in wieder_lager or det > wieder_lager[sku]["detected_at"]: wieder_lager[sku] = c
        if ct == "neue_vorbestellung" and det >= cutoff_pre:
            if sku not in vorb or det > vorb[sku]["detected_at"]: vorb[sku] = c
    neu = []
    for sku, ch in neu_lager.items():
        p = pm.get(sku)
        if p is None:
            # Fallback: Produkt nicht mehr in CSV (verkauft), nutze gespeicherte Daten
            if "name" not in ch or should_skip(ch.get("name","")):
                continue
            p = {"name": ch["name"], "price": ch["price"], "url": ch.get("url",""),
                 "image": ch.get("image",""), "release": ch.get("release",""),
                 "bestprice": ch.get("bestprice","N"), "available": "ausverkauft",
                 "supply": "", "sku": sku}
        elif should_skip(p["name"]):
            continue
        p["change_reason"] = "Neu im Shop"; p["detected_at"] = ch["detected_at"]; neu.append(p)
    wieder = []
    wieder_skipped = 0
    for sku, ch in wieder_lager.items():
        if sku in neu_lager: continue
        p = pm.get(sku)
        if p is None:
            if "name" not in ch or should_skip(ch.get("name","")):
                continue
            p = {"name": ch["name"], "price": ch["price"], "url": ch.get("url",""),
                 "image": ch.get("image",""), "release": ch.get("release",""),
                 "bestprice": ch.get("bestprice","N"), "available": "ausverkauft",
                 "supply": "", "sku": sku}
        elif should_skip(p["name"]):
            continue
        # "Wieder da" nur DE-Versionen — sonst ufern EN-Restocks aus
        if detect_language(p["name"]) != "DE":
            wieder_skipped += 1
            continue
        p["change_reason"] = "Wieder auf Lager"; p["detected_at"] = ch["detected_at"]; wieder.append(p)
    vl = []
    for sku, ch in vorb.items():
        if sku in neu_lager or sku in wieder_lager: continue
        p = pm.get(sku)
        if p is None:
            if "name" not in ch or should_skip(ch.get("name","")):
                continue
            p = {"name": ch["name"], "price": ch["price"], "url": ch.get("url",""),
                 "image": ch.get("image",""), "release": ch.get("release",""),
                 "bestprice": ch.get("bestprice","N"), "available": "ausverkauft",
                 "supply": "Vorbestellung", "sku": sku}
        elif should_skip(p["name"]):
            continue
        p["change_reason"] = "Neue Vorbestellung"; p["detected_at"] = ch["detected_at"]; vl.append(p)
    neu.sort(key=lambda p: p.get("detected_at",""), reverse=True)
    wieder.sort(key=lambda p: p.get("detected_at",""), reverse=True)
    vl.sort(key=lambda p: p.get("detected_at",""), reverse=True)
    return neu, wieder, vl

# ─── TRACKER-KERN ────────────────────────────────────────────────────────────

def run_tracker(days_new, days_preorder):
    log = []
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    raw = download_csv()
    products = parse_csv(raw)
    games = [p for p in products if not should_skip(p["name"])]
    log.append(f"✅ {len(games)} Spiele geladen")
    old = load_snapshot(); changes = load_changes()
    if old:
        mode = "diff"; nc = detect_changes(products, old, timestamp)
        log.append(f"🔍 {len(nc)} neue Änderungen"); changes.extend(nc)
    else:
        mode = "initial"; seed = initial_seed(products, days_new, days_preorder, timestamp)
        log.append(f"📅 Erster Lauf: {len(seed)} Seed"); changes.extend(seed)
    changes = cleanup_changes(changes, keep_days=max(days_new, days_preorder)+7)
    save_changes(changes); save_snapshot(products, timestamp)
    cleanup_old_snapshots(keep_days=max(days_new, days_preorder)+7)
    neu, wieder, vorb = build_neuheiten(products, changes, days_new, days_preorder)
    log.append(f"🆕 {len(neu)} Neuheiten, {len(wieder)} wieder da (DE), {len(vorb)} Vorbestellungen")
    return neu, wieder, vorb, mode, days_new, days_preorder, log

# ─── HTML-GENERIERUNG ────────────────────────────────────────────────────────

def render_deal_cards(items, accent="#20228a"):
    """Amazon-Stil: Flexbox-Cards mit Bild, Name, Preis, Badges."""
    if not items:
        return '<p style="color:#6b7280;text-align:center;padding:2rem 0;">Keine Neuheiten in diesem Zeitraum.</p>'
    cards = []
    for p in items:
        name = escape(p["name"])
        price = format_price(p["price"])
        url = escape(add_affiliate(p.get("url", "#")))
        image = p.get("image", "")
        lang = detect_language(p["name"])
        bestprice = p.get("bestprice", "N")
        release_str = ""
        release_future = False
        if p.get("release"):
            try:
                rd = datetime.strptime(p["release"], "%Y-%m-%d")
                release_str = rd.strftime("%d.%m.%Y")
                if rd > datetime.now():
                    release_future = True
            except: pass

        # Badges
        badges = []
        if lang:
            badges.append(f'<span style="display:inline-block;background:#e8f5e9;color:#16a34a;font-size:0.65rem;font-weight:700;padding:1px 6px;border-radius:3px;text-transform:uppercase;margin-right:4px;">{escape(lang)}</span>')
        if bestprice == "Y":
            badges.append('<span style="display:inline-block;background:#fff3e0;color:#ea580c;font-size:0.65rem;font-weight:700;padding:1px 6px;border-radius:3px;text-transform:uppercase;margin-right:4px;">Best-Price</span>')
        if release_str:
            if release_future:
                badges.append(f'<span style="display:inline-block;background:#ffebee;color:#dc2626;font-size:0.65rem;font-weight:700;padding:1px 6px;border-radius:3px;text-transform:uppercase;margin-right:4px;">ET: {release_str} ⚠</span>')
            else:
                badges.append(f'<span style="display:inline-block;background:#f3e8ff;color:#7c3aed;font-size:0.65rem;font-weight:700;padding:1px 6px;border-radius:3px;text-transform:uppercase;margin-right:4px;">Ersch. {release_str}</span>')

        badge_html = "".join(badges)

        # Ausverkauft-Badge
        if p.get("available") == "ausverkauft":
            badge_html += ' <span style="display:inline-block;background:#fef2f2;color:#dc2626;font-size:0.65rem;font-weight:700;padding:1px 6px;border-radius:3px;text-transform:uppercase;margin-right:4px;">Ausverkauft</span>'

        # Image — Cloudflare schützt /media/ vor Hotlinking.
        # Bild-URL wird als data-fallback gespeichert, aber nicht als <img> eingebunden.
        # Im Browser-Kontext (gleiche Session) würden sie laden, cross-origin nicht.
        img_html = ""

        cards.append(f'''<div style="display:flex;gap:0.8rem;padding:1rem 0;border-bottom:1px solid #eee;align-items:flex-start;">
<div style="width:4px;min-height:50px;background:{accent};border-radius:2px;flex-shrink:0;align-self:stretch;"></div>
<div style="flex:1;min-width:0;">
  <a href="{url}" target="_blank" style="color:#1a1d23;text-decoration:none;font-weight:600;font-size:0.95rem;line-height:1.3;">{name}</a>
  <div style="margin-top:4px;">{badge_html}</div>
  <div style="margin-top:6px;display:flex;align-items:baseline;gap:0.5rem;flex-wrap:wrap;">
    <span style="color:{accent};font-size:1.1rem;font-weight:700;">{price} €</span>
    <a href="{url}" target="_blank" style="color:{accent};text-decoration:none;font-size:0.8rem;font-weight:700;padding:4px 12px;border:1.5px solid {accent};border-radius:99px;display:inline-block;transition:all .2s;" onmouseover="this.style.background='{accent}';this.style.color='#fff';" onmouseout="this.style.background='transparent';this.style.color='{accent}';">Kaufen →</a>
  </div>
</div>
</div>''')
    return "".join(cards)


def _render_embed_cards(items):
    """Reine Cards ohne Container (für Copy-Button)."""
    return render_deal_cards(items)


def generate_html(neu, wieder, vorb, days_new, days_preorder, mode, sections="all"):
    """Generate HTML. sections: 'all', 'neu', 'neu_wieder', 'vorbestellung'"""
    now_str = datetime.now().strftime("%d.%m.%Y")

    show_neu = sections in ("all", "neu", "neu_wieder")
    show_wieder = sections in ("all", "neu_wieder")
    show_vorb = sections in ("all", "vorbestellung")

    neu_cards = render_deal_cards(neu, accent="#20228a") if (show_neu and neu) else ""
    wieder_cards = render_deal_cards(wieder, accent="#20228a") if (show_wieder and wieder) else ""
    vorb_cards = render_deal_cards(vorb, accent="#20228a") if (show_vorb and vorb) else ""

    sections_html = []
    if show_neu:
        if neu:
            sections_html.append(f'<h2 style="font-size:1.3rem;font-weight:700;margin:0 0 0.3rem;">Neu im Shop ({len(neu)})</h2>')
            sections_html.append(f'<p style="color:#6b7280;font-size:0.82rem;margin:0 0 1rem;">Brandneue Spiele · Stand: {now_str}</p>')
            sections_html.append(neu_cards)
        else:
            sections_html.append('<p style="color:#6b7280;text-align:center;padding:1rem 0;">Keine brandneuen Spiele in diesem Zeitraum.</p>')

    if show_wieder:
        if wieder:
            sections_html.append(f'<div style="margin-top:2rem;">')
            sections_html.append(f'<h2 style="font-size:1.3rem;font-weight:700;margin:0 0 0.3rem;">Wieder auf Lager ({len(wieder)})</h2>')
            sections_html.append(f'<p style="color:#6b7280;font-size:0.82rem;margin:0 0 1rem;">Nachfrage hat sich gelohnt — diese Spiele sind zurück</p>')
            sections_html.append(wieder_cards)
            sections_html.append(f'</div>')
        else:
            sections_html.append('<div style="margin-top:2rem;"><p style="color:#6b7280;text-align:center;padding:1rem 0;">Keine Nachschub-Entdeckungen in diesem Zeitraum.</p></div>')

    if show_vorb:
        if vorb:
            sections_html.append(f'<div style="margin-top:2rem;">')
            sections_html.append(f'<h2 style="font-size:1.3rem;font-weight:700;margin:0 0 0.3rem;">Neue Vorbestellungen ({len(vorb)})</h2>')
            sections_html.append(f'<p style="color:#6b7280;font-size:0.82rem;margin:0 0 1rem;">Demnächst erscheinend · Stand: {now_str}</p>')
            sections_html.append(vorb_cards)
            sections_html.append(f'</div>')
        else:
            sections_html.append('<div style="margin-top:2rem;"><p style="color:#6b7280;text-align:center;padding:1rem 0;">Keine neuen Vorbestellungen in diesem Zeitraum.</p></div>')

    embed_code = f'''<div style="max-width:680px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:#1a1d23;line-height:1.5;padding:0 1rem;">
{"".join(sections_html)}
<p style="color:#6b7280;font-size:0.75rem;margin-top:1.5rem;text-align:center;">
  <a href="{escape(add_affiliate('https://www.fantasywelt.de'))}" target="_blank" style="color:#20228a;text-decoration:none;">Alle Angebote auf Fantasywelt.de →</a><br>
  Preise und Verfügbarkeit können sich schnell ändern.
</p>
</div>'''

    # Volle HTML-Seite (für Server)
    full_page = f'''<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fantasywelt.de — Neuheiten</title>
<style>
*{{box-sizing:border-box;margin:0;padding:0}}
body{{background:#fff;color:#1a1d23;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.5;padding:1.5rem 1rem 3rem}}
h2{{font-size:1.3rem;font-weight:700;margin:0 0 0.3rem}}
@media(max-width:600px){{
  body{{padding:0.5rem 0.5rem 2rem}}
}}
</style>
</head>
<body>
{embed_code}
</body>
</html>'''

    return full_page


# ─── WEBSERVER ───────────────────────────────────────────────────────────────

class Handler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args): print(f"🌐 {args[0]}") if args else None

    def do_GET(self):
        parsed = urlparse(self.path)
        if parsed.path == "/refresh": self._handle_refresh(parsed)
        elif parsed.path in ("/","/index.html"): self._serve_html()
        else: self.send_error(404)

    def _serve_html(self):
        if not OUTPUT_FILE.exists(): self._run_and_save(DEFAULT_DAYS_NEW, DEFAULT_DAYS_PREORDER)
        try:
            with open(OUTPUT_FILE, "r", encoding="utf-8") as f: content = f.read().encode("utf-8")
            self.send_response(200); self.send_header("Content-Type","text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(content))); self.end_headers(); self.wfile.write(content)
        except Exception as e: self.send_error(500, str(e))

    def _handle_refresh(self, parsed):
        qs = parse_qs(parsed.query)
        try:
            dn = max(1, min(90, int(qs.get("days_new",[str(DEFAULT_DAYS_NEW)])[0])))
            dp = max(1, min(90, int(qs.get("days_preorder",[str(DEFAULT_DAYS_PREORDER)])[0])))
        except: dn, dp = DEFAULT_DAYS_NEW, DEFAULT_DAYS_PREORDER
        try:
            self._run_and_save(dn, dp)
            resp = json.dumps({"ok":True,"message":f"{dn}T Neuheiten / {dp}T Vorbestellungen aktualisiert"})
        except Exception as e: resp = json.dumps({"ok":False,"message":str(e)})
        self.send_response(200); self.send_header("Content-Type","application/json")
        self.send_header("Content-Length",str(len(resp))); self.end_headers(); self.wfile.write(resp.encode("utf-8"))

    def _run_and_save(self, dn, dp):
        print(f"\n{'='*50}\n🔄 Tracker: {dn}T / {dp}T\n{'='*50}")
        neu, wieder, vorb, mode, dn2, dp2, log = run_tracker(dn, dp)
        for l in log: print(f"   {l}")
        html = generate_html(neu, wieder, vorb, dn2, dp2, mode)
        DATA_DIR.mkdir(parents=True, exist_ok=True)
        with open(OUTPUT_FILE, "w", encoding="utf-8") as f: f.write(html)
        print(f"   📄 {OUTPUT_FILE}\n{'='*50}\n")

# ─── MAIN ────────────────────────────────────────────────────────────────────

def main():
    port = DEFAULT_PORT
    once = "--once" in sys.argv
    sections = "all"
    if "--sections" in sys.argv:
        idx = sys.argv.index("--sections")
        if idx+1 < len(sys.argv):
            sections = sys.argv[idx+1]
    if "--only" in sys.argv:
        idx = sys.argv.index("--only")
        if idx+1 < len(sys.argv):
            sections = sys.argv[idx+1]
    if "--port" in sys.argv:
        idx = sys.argv.index("--port")
        if idx+1 < len(sys.argv):
            try: port = int(sys.argv[idx+1])
            except: pass

    if once:
        dn, dp = DEFAULT_DAYS_NEW, DEFAULT_DAYS_PREORDER
        for arg, t in [("--days-new","n"),("--days-preorder","p")]:
            if arg in sys.argv:
                idx = sys.argv.index(arg)
                if idx+1 < len(sys.argv):
                    try:
                        v = int(sys.argv[idx+1])
                        if t=="n": dn=v
                        else: dp=v
                    except: pass
        print(f"⏱️ Einmal: {dn}T / {dp}T (sections={sections})\n")
        neu, wieder, vorb, mode, dn2, dp2, log = run_tracker(dn, dp)
        for l in log: print(f"   {l}")
        html = generate_html(neu, wieder, vorb, dn2, dp2, mode, sections=sections)
        DATA_DIR.mkdir(parents=True, exist_ok=True)
        with open(OUTPUT_FILE, "w", encoding="utf-8") as f: f.write(html)
        print(f"\n📄 {OUTPUT_FILE}")
        print(f"📦 {len(neu)} Neuheiten | ♻️ {len(wieder)} wieder da | 🔮 {len(vorb)} Vorbestellungen\n✨ Fertig!")
    else:
        print(f"🚀 Fantasywelt Tracker → http://localhost:{port}\n   Strg+C zum Beenden\n")
        server = HTTPServer(("0.0.0.0", port), Handler)
        try: server.serve_forever()
        except KeyboardInterrupt: print("\n👋 Beendet."); server.server_close()

if __name__ == "__main__":
    main()
