#!/usr/bin/env python3
"""Spiele-Offensive.de Deal-Scraper v2
Scraped Startseiten-Slider, Gruppendeals und Preisaktionen.
"""

import re
import sys
import urllib.request
from datetime import datetime
from html import escape, unescape

USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
BASE_URL = "https://www.spiele-offensive.de"
NOW = datetime.now().strftime("%d.%m.%Y %H:%M")

def fetch(url, timeout=15):
    req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            raw = resp.read()
            try:
                return raw.decode("utf-8")
            except UnicodeDecodeError:
                return raw.decode("iso-8859-1")
    except Exception as e:
        print(f"  [WARN] {e}", file=sys.stderr)
        return ""

def clean_name(s):
    """Fix encoding artifacts in names."""
    s = s.replace("Ã¼", "ü").replace("Ã¶", "ö").replace("Ã¤", "ä").replace("ÃŸ", "ß")
    s = s.replace("Ã-", "Ü").replace("Ã„", "Ä").replace("Ã–", "Ö")
    s = s.replace("Ã©", "é").replace("Ã¨", "è")
    s = s.replace("&amp;", "&").replace("&quot;", '"')
    return unescape(s).strip()

def parse_slider_deals(html):
    """Parse deals from the start page slider."""
    deals = []
    
    # Find all unique slider entries by their onclick handler
    # Each entry has: registerPromotionSelection(dataLayer, promoId, `name`, `creative`, artId, `artName`, price)
    # with backticks in inline HTML attributes
    pattern = r"registerPromotionSelection\(dataLayer,\s*(\d+),\s*`([^`]*)`,\s*`([^`]*)`,\s*(\d+),\s*`([^`]+)`,\s*([\d.]+)\)"
    
    seen_promo = set()
    for m in re.finditer(pattern, html):
        promo_id = m.group(1)
        art_id = m.group(4)
        art_name = clean_name(m.group(5))
        new_price = m.group(6).replace(".", ",")
        
        if promo_id in seen_promo:
            continue
        seen_promo.add(promo_id)
        
        # Find context around this match (the surrounding slider LI)
        ctx_start = max(0, html.rfind("<li ", 0, m.start()))
        if ctx_start < 0:
            ctx_start = max(0, m.start() - 3000)
        ctx_end = min(len(html), m.end() + 3000)
        ctx = html[ctx_start:ctx_end]
        
        # Extract old price: "statt <span ...>XX,XX €</span>"
        old_match = re.search(r"statt\s*<span[^>]*text-decoration:\s*line-through[^>]*>\s*(\d+,\d+)", ctx)
        
        # Extract new price from the "nur" section (split across spans)
        nur_match = re.search(r"nur\s*<span[^>]*>\s*(\d+)</span>,<span[^>]*>\s*(\d+)</span>\s*&euro;", ctx)
        if nur_match:
            new_price = f"{nur_match.group(1)},{nur_match.group(2)}"
        else:
            # Simpler pattern
            nur_match2 = re.search(r"nur\s*<[^>]*>\s*(\d+,\d+)\s*&euro;", ctx)
            if nur_match2:
                new_price = nur_match2.group(1)
        
        # Extract discount
        rabatt_match = re.search(r"(\d+)\s*%\s*Rabatt", ctx)
        rabatt = int(rabatt_match.group(1)) if rabatt_match else None
        
        # Extract end date
        date_match = re.search(r"Nur bis (\d+\.\d+\.\d+)", ctx)
        end_date = date_match.group(1) if date_match else None
        
        # Extract stock
        stock_match = re.search(r"nur (\d+)\s*Stück", ctx)
        stock = int(stock_match.group(1)) if stock_match else None
        
        old_price = old_match.group(1) if old_match else None
        
        if old_price and new_price:
            # Calculate discount if not explicitly stated
            if rabatt is None:
                try:
                    old_f = float(old_price.replace(",", "."))
                    new_f = float(new_price.replace(",", "."))
                    if old_f > 0:
                        rabatt = round((1 - new_f / old_f) * 100)
                except:
                    pass
            
            deals.append({
                "name": art_name,
                "art_id": art_id,
                "old_price": old_price,
                "new_price": new_price,
                "rabatt_pct": rabatt,
                "end_date": end_date,
                "stock": stock,
                "url": f"/Spiel/{art_id}",
                "source": "Startseite",
            })
    
    return deals


def parse_gruppendeal(html, grid_id):
    """Parse a single Gruppendeal page.

    The page has multiple price tiers like:
      Preis: nur 20,00 €* statt 39,00 €* (UVP des Herstellers)
      Rabatt: 48,7%  Ersparnis: 19,00 €
    We extract each tier and pick the cheapest.
    """
    deals = []

    # Get product name from H1
    h1_match = re.search(r"<h1[^>]*>(.*?)</h1>", html, re.DOTALL)
    if not h1_match:
        return deals
    name = clean_name(re.sub(r"<[^>]+>", "", h1_match.group(1)))

    # Find article ID
    art_id_match = re.search(r"artikel_anzeigen&(?:amp;)?aid=([^\"']+)", html)
    art_id = art_id_match.group(1) if art_id_match else None

    # Find all "nur X €* statt Y €* (UVP)" patterns
    # The prices are often split across HTML spans: "nur <span>29,</span><span>00</span> &euro;*"
    # Strategy: find "nur" sections, strip inline tags to get clean text, then regex
    tiers = []
    for m in re.finditer(r"nur\s+<", html):
        # Grab a chunk from "nur" through the UVP closing paren
        chunk = html[m.start() : m.start() + 400]
        # Strip HTML tags to get clean text
        text = re.sub(r"<[^>]+>", "", chunk)
        text = re.sub(r"\s+", " ", text).strip()
        # Now parse: "nur 29,00 &euro;* statt 50,00 &euro;* (UVP des Herstellers)"
        match = re.search(
            r"nur\s+(\d+,\d+)\s*&euro;\s*\*\s*statt\s+(\d+,\d+)\s*&euro;\s*\*\s*\(UVP",
            text,
        )
        if match and (match.group(1), match.group(2)) not in tiers:
            tiers.append((match.group(1), match.group(2)))

    # Rabatt percentages (these are also split across spans)
    rabatt_raw = re.findall(r"Rabatt:\s*<[^>]*>\s*(\d+)[,.](\d)", html)
    rabatte = [int(r[0]) for r in rabatt_raw]  # e.g. 42 from 42,0%

    for i, (new_p, old_p) in enumerate(tiers):
        rabatt = None
        if i < len(rabatte):
            rabatt = rabatte[i]
        deals.append({
            "name": name,
            "art_id": art_id,
            "old_price": old_p,
            "new_price": new_p,
            "rabatt_pct": rabatt,
            "end_date": None,
            "stock": None,
            "url": f"/index.php?cmd=gruppendeal&grid={grid_id}",
            "source": "Gruppendeal",
        })

    # If we have multiple tiers, keep only the cheapest
    if len(deals) > 1:
        deals.sort(key=lambda d: float(d["new_price"].replace(",", ".")))
        deals = deals[:1]

    return deals


def parse_preisaktion(html, aktion_id):
    """Parse a Preisaktion page."""
    deals = []
    pattern = r"registerItemSelection\(dataLayer,\s*(\d+),\s*`([^`]+)`,\s*([\d.]+)\)"
    
    seen_ids = set()
    for m in re.finditer(pattern, html):
        art_id, art_name, price = m.groups()
        if art_id in seen_ids:
            continue
        seen_ids.add(art_id)
        
        # Find URL
        ctx_start = max(0, m.start() - 600)
        ctx = html[ctx_start:m.end() + 200]
        url_match = re.search(r'Spiel/([^\"\']+-\d+\.html)', ctx)
        url = f"/Spiel/{url_match.group(1)}" if url_match else None
        
        deals.append({
            "name": clean_name(art_name),
            "art_id": art_id,
            "old_price": None,
            "new_price": price.replace(".", ","),
            "rabatt_pct": None,
            "end_date": None,
            "stock": None,
            "url": url,
            "source": f"Preisaktion {aktion_id}",
        })
    
    return deals


def fetch_article_old_price(html):
    """Try to find old/UVP price from an article page."""
    # Find UVP or strikethrough price
    uvp_match = re.search(r"(?:UVP|unverbindliche\s*Preisempfehlung)[^<]*</[^>]*>\s*(\d+,\d+)\s*(?:&euro;|€)", html, re.IGNORECASE)
    if uvp_match:
        return uvp_match.group(1)
    
    # Find strikethrough price
    strike_match = re.search(r"text-decoration:\s*line-through[^>]*>\s*(\d+,\d+)\s*(?:&euro;|€)", html)
    if strike_match:
        return strike_match.group(1)
    
    return None


def parse_sonderangebote(html, page=0):
    """Parse the Sonderangebote (special offers) page.

    Each item has:
    - <span class="discount">-<span>80</span>%</span>  (discount percentage)
    - registerItemSelection(dataLayer, artId, `name`, price)
    - nur <span>X</span>,<span>XX</span> &euro;  (new price split across spans)
    """
    deals = []
    
    # Split into list items
    items = re.split(r'<li class="ala">', html)[1:]  # Skip first split
    
    for item in items:
        # Discount
        discount_match = re.search(r'class="discount">\s*-<span>(\d+)</span>%', item)
        discount = int(discount_match.group(1)) if discount_match else None
        
        # registerItemSelection
        reg_match = re.search(r"registerItemSelection\(dataLayer,\s*(\d+),\s*`([^`]+)`,\s*([\d.]+)\)", item)
        if not reg_match:
            continue
        art_id, art_name, price = reg_match.groups()
        art_name = clean_name(art_name)
        new_price = price.replace(".", ",")
        
        # URL
        url_match = re.search(r'Spiel/([^\"\']+-\d+\.html)', item)
        url = f"/Spiel/{url_match.group(1)}" if url_match else None
        
        # Calculate old price from discount
        old_price = None
        if discount and discount > 0:
            try:
                new_f = float(price)
                old_f = new_f / (1 - discount / 100)
                old_price = f"{old_f:.2f}".replace(".", ",")
            except:
                pass
        
        deals.append({
            "name": art_name,
            "art_id": art_id,
            "old_price": old_price,
            "new_price": new_price,
            "rabatt_pct": discount,
            "end_date": None,
            "stock": None,
            "url": url,
            "source": "Sonderangebot",
        })
    
    return deals


def discover_ids(html):
    """Discover all grid and aktion IDs from HTML."""
    grids = set()
    aktions = set()
    
    for m in re.finditer(r"cmd=gruppendeal&(?:amp;)?grid=(\d+)", html):
        grids.add(m.group(1))
    for m in re.finditer(r"cmd=preisaktion&(?:amp;)?id=(\d+)", html):
        aktions.add(m.group(1))
    
    return sorted(grids, key=int), sorted(aktions, key=int)


def generate_html(deals, output_path):
    """Generate styled HTML page."""
    deals_sorted = sorted(deals, key=lambda d: d.get("rabatt_pct") or 0, reverse=True)
    
    html_parts = [f"""<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spiele-Offensive.de Deals – {NOW}</title>
<style>
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:#f0f2f5; color:#1a1a2e; padding:20px; }}
.header {{ background:linear-gradient(135deg,#e63946,#c1121f); color:white; padding:30px; border-radius:12px; margin-bottom:24px; text-align:center; }}
.header h1 {{ font-size:28px; margin-bottom:6px; }}
.header p {{ font-size:14px; opacity:0.9; }}
.stats {{ display:flex; gap:16px; justify-content:center; margin-bottom:24px; flex-wrap:wrap; }}
.stat {{ background:white; padding:14px 24px; border-radius:10px; box-shadow:0 2px 8px rgba(0,0,0,0.08); text-align:center; }}
.stat .num {{ font-size:26px; font-weight:700; color:#e63946; }}
.stat .label {{ font-size:12px; color:#666; margin-top:2px; }}
.grid {{ display:grid; grid-template-columns:repeat(auto-fill,minmax(340px,1fr)); gap:16px; }}
.card {{ background:white; border-radius:12px; padding:20px; box-shadow:0 2px 12px rgba(0,0,0,0.06); transition:transform 0.15s,box-shadow 0.15s; border-left:4px solid #e63946; display:flex; flex-direction:column; }}
.card:hover {{ transform:translateY(-2px); box-shadow:0 6px 20px rgba(0,0,0,0.12); }}
.card-top {{ display:flex; justify-content:space-between; align-items:flex-start; gap:12px; }}
.card-name {{ font-size:16px; font-weight:600; color:#1a1a2e; line-height:1.3; flex:1; }}
.card-badge {{ padding:4px 10px; border-radius:20px; font-size:13px; font-weight:700; white-space:nowrap; color:white; }}
.card-prices {{ margin-top:14px; display:flex; align-items:baseline; gap:12px; }}
.card-old {{ font-size:15px; color:#999; text-decoration:line-through; }}
.card-new {{ font-size:24px; font-weight:700; color:#e63946; }}
.card-bottom {{ margin-top:14px; font-size:12px; color:#888; display:flex; gap:12px; flex-wrap:wrap; }}
.card-link {{ margin-top:auto; padding-top:14px; }}
.card-link a {{ display:inline-block; background:#e63946; color:white; padding:8px 18px; border-radius:6px; text-decoration:none; font-size:13px; font-weight:500; }}
.card-link a:hover {{ background:#c1121f; }}
.source-tag {{ font-size:11px; background:#f0f0f0; padding:2px 8px; border-radius:4px; color:#666; }}
.footer {{ margin-top:30px; text-align:center; font-size:11px; color:#999; }}
</style>
</head>
<body>
<div class="header">
<h1>🎲 Spiele-Offensive.de – Aktuelle Deals</h1>
<p>Stand: {NOW} – Automatisch aktualisiert</p>
</div>
<div class="stats">
<div class="stat"><div class="num">{len(deals)}</div><div class="label">Angebote</div></div>
"""]
    
    if deals_sorted:
        max_rab = max((d.get("rabatt_pct") or 0) for d in deals_sorted)
        sum_price = sum(float(d["new_price"].replace(",", ".")) for d in deals_sorted)
        html_parts.append(f'<div class="stat"><div class="num">{max_rab}%</div><div class="label">Max-Rabatt</div></div>')
        html_parts.append(f'<div class="stat"><div class="num">{sum_price/len(deals_sorted):.0f} €</div><div class="label">Ø-Preis</div></div>')
    
    html_parts.append('</div><div class="grid">')
    
    for d in deals_sorted:
        name = escape(d.get("name", "?"))
        old_price = d.get("old_price", "")
        new_price = d.get("new_price", "")
        rabatt = d.get("rabatt_pct")
        stock = d.get("stock")
        source = d.get("source", "")
        art_id = d.get("art_id", "")
        
        if art_id:
            full_url = f"{BASE_URL}/index.php?cmd=artikel_anzeigen&aid={art_id}&pid=505"
        elif d.get("url"):
            full_url = f"{BASE_URL}{d['url']}"
            full_url = f"{full_url}&pid=505" if "?" in full_url else f"{full_url}?pid=505"
        else:
            full_url = f"{BASE_URL}?pid=505"
        
        bg = "#e63946"
        if rabatt and rabatt >= 50:
            bg = "#e63946"
        elif rabatt and rabatt >= 30:
            bg = "#f39c12"
        elif rabatt:
            bg = "#3498db"
        
        html_parts.append(f'<div class="card"><div class="card-top"><div class="card-name">{name}</div>')
        if rabatt:
            html_parts.append(f'<div class="card-badge" style="background:{bg}">-{rabatt}%</div>')
        html_parts.append('</div><div class="card-prices">')
        if old_price:
            html_parts.append(f'<span class="card-old">{old_price} €</span>')
        html_parts.append(f'<span class="card-new">{new_price} €</span>')
        html_parts.append('</div><div class="card-bottom">')
        html_parts.append(f'<span class="source-tag">{escape(source)}</span>')
        if stock is not None and stock > 0:
            html_parts.append(f'<span>📦 {stock} Stück</span>')
        if d.get("end_date"):
            html_parts.append(f'<span>⏰ bis {d["end_date"]}</span>')
        html_parts.append(f'</div><div class="card-link"><a href="{full_url}" target="_blank">Zum Angebot →</a></div></div>')
    
    html_parts.append(f'</div><div class="footer"><p>brettspiel-news.de – Spiele-Offensive Deal-Scraper – {NOW}</p></div></body></html>')
    
    with open(output_path, "w", encoding="utf-8") as f:
        f.write("".join(html_parts))
    
    print(f"\nHTML written to {output_path}")


def main():
    html_output = "/home/hermes/workspace/spiele_offensive_deals.html"
    all_deals = []
    
    # 1. Homepage slider
    print("=== Startseite ===")
    home_html = fetch(BASE_URL, timeout=15)
    slider = parse_slider_deals(home_html)
    print(f"Slider-Deals: {len(slider)}")
    for d in slider:
        print(f"  {d['new_price']}€ (-{d.get('rabatt_pct','?')}%) | {d['name'][:50]}")
    all_deals.extend(slider)
    
    # Discover IDs
    grids, aktions = discover_ids(home_html)
    print(f"\nGruppendeal-Grids: {grids}")
    print(f"Preisaktion-IDs: {aktions}")
    
    # 2. Gruppendeals
    for gid in grids:
        print(f"\n--- Gruppendeal Grid {gid} ---")
        html = fetch(f"{BASE_URL}/index.php?cmd=gruppendeal&grid={gid}", timeout=15)
        if html:
            deals = parse_gruppendeal(html, gid)
            for d in deals:
                print(f"  {d['new_price']}€ (-{d.get('rabatt_pct','?')}%) | {d['name'][:60]}")
                all_deals.extend(deals)
    
    # 3. Preisaktionen
    for aid in aktions:
        print(f"\n--- Preisaktion {aid} ---")
        html = fetch(f"{BASE_URL}/index.php?cmd=preisaktion&id={aid}&anz=100", timeout=15)
        if html:
            deals = parse_preisaktion(html, aid)
            print(f"  {len(deals)} Artikel (Preise ohne UVP)")
            
            # Try to get old prices for first 10
            for d in deals[:10]:
                if d["art_id"] and d.get("url"):
                    art_url = f"{BASE_URL}{d['url']}"
                    art_html = fetch(art_url, timeout=10)
                    if art_html:
                        old = fetch_article_old_price(art_html)
                        if old:
                            d["old_price"] = old
                            try:
                                new_f = float(d["new_price"].replace(",", "."))
                                old_f = float(old.replace(",", "."))
                                if old_f > 0:
                                    d["rabatt_pct"] = round((1 - new_f / old_f) * 100)
                            except:
                                pass
            
            all_deals.extend(deals)
    
    # 4. Sonderangebote (paginated)
    print(f"\n--- Sonderangebote ---")
    sonderangebote_all = []
    max_pages = 10  # Safety limit
    for page in range(max_pages):
        url = f"{BASE_URL}/Kat/Sonderangebote.html?sse={page}"
        html = fetch(url, timeout=15)
        if not html:
            break
        deals = parse_sonderangebote(html, page)
        if not deals:
            break
        print(f"  Page {page}: {len(deals)} Artikel")
        sonderangebote_all.extend(deals)
        # Check if there's a next page by looking for sse={page+1} link
        if f"sse={page+1}" not in html:
            break
    print(f"  Total Sonderangebote: {len(sonderangebote_all)}")
    all_deals.extend(sonderangebote_all)
    
    # Deduplicate: merge slider + Gruppendeal for same game; prefer Gruppendeal data
    # Also merge Preisaktion duplicates
    by_name = {}  # normalized name -> best deal
    preisaktion_items = []  # keep separate (no old prices)
    
    # First pass: process Gruppendeal + Slider + Sonderangebote
    for d in all_deals:
        src = d.get("source", "")
        if "Preisaktion" in src and "Sonderangebot" not in src:
            preisaktion_items.append(d)
            continue
        
        norm = d.get("name", "").lower().strip()
        if norm not in by_name:
            by_name[norm] = d
        else:
            existing = by_name[norm]
            # Prefer deals with better discounts; Gruppendeal usually better
            new_rab = d.get("rabatt_pct") or 0
            old_rab = existing.get("rabatt_pct") or 0
            is_gruppendeal = "Gruppendeal" in d.get("source", "")
            is_existing_gd = "Gruppendeal" in existing.get("source", "")
            
            if is_gruppendeal and not is_existing_gd:
                by_name[norm] = d
            elif new_rab > old_rab:
                by_name[norm] = d
    
    # Filter out non-deals: slider entries with <5% discount and no stock info
    unique = []
    for d in by_name.values():
        rab = d.get("rabatt_pct") or 0
        if rab < 5 and d.get("stock") is None:
            continue  # Not a real deal
        unique.append(d)
    
    # Add Preisaktion items (dedup by art_id)
    seen_pa = set()
    for d in preisaktion_items:
        key = d.get("art_id", d.get("name", ""))
        if key not in seen_pa:
            seen_pa.add(key)
            unique.append(d)
    
    print(f"\n{'='*60}")
    print(f"Total unique deals: {len(unique)}")
    for d in unique:
        rab = f" (-{d.get('rabatt_pct')}%)" if d.get('rabatt_pct') else ""
        old = f" (statt {d.get('old_price')}€)" if d.get('old_price') else ""
        print(f"  {d['new_price']}€{rab}{old} | {d['name'][:60]} | {d.get('source','')}")
    
    generate_html(unique, html_output)
    return unique


if __name__ == "__main__":
    main()
