#!/usr/bin/env python3
"""
BSN Publish Gatekeeper — VALIDATE → PUBLISH → VERIFY

Kein Artikel geht live ohne diesen dreistufigen Check.
Ersetzt alle manuellen curl/PATCH-Workflows.
"""

import re
import json
import sys
import urllib.request
import urllib.error

TOKEN_FILE = "/home/hermes/.hermes/joomla_token.txt"
API_BASE = "https://www.brettspiel-news.de/api/index.php/v1/content/articles"

# ── LOAD TOKEN ────────────────────────────────────────────
with open(TOKEN_FILE) as f:
    TOKEN = f.read().strip()

HEADERS = {
    "Accept": "application/vnd.api+json",
    "Content-Type": "application/json",
    "X-Joomla-Token": TOKEN,
}

HEADERS_GET = {
    "Accept": "application/vnd.api+json",
    "X-Joomla-Token": TOKEN,
}


def load_html(path: str) -> str:
    """Lade HTML-Datei."""
    with open(path) as f:
        return f.read()


def extract_article_body(html: str) -> tuple[str, list[str]]:
    """Extrahiere <article>-Body, strippe problematische Elemente.
    Returns: (bereinigter_body, fehlerliste)
    """
    errors = []
    m = re.search(r"<article>(.*?)</article>", html, re.DOTALL)
    if not m:
        return "", ["KEIN <article>-Tag gefunden!"]
    body = m.group(1)

    # ⛔ STRIP: <title> — würde doppelt erscheinen (Joomla hat eigenen Title)
    title_match = re.search(r"<title>.*?</title>", body, re.DOTALL)
    if title_match:
        errors.append(f"⛔ <title> im <article>-Body gefunden — wird gestrippt!")
        body = re.sub(r"<title>.*?</title>\s*", "", body, count=1, flags=re.DOTALL)

    # ⛔ STRIP: <h1> — Joomla rendert eigenen Title als <h1>
    h1_match = re.search(r"<h1>.*?</h1>", body, re.DOTALL)
    if h1_match:
        errors.append(f"⛔ <h1> im <article>-Body — wird gestrippt!")
        body = re.sub(r"<h1>.*?</h1>\s*", "", body, count=1, flags=re.DOTALL)

    # ⛔ STRIP: erstes <h2> — dupliziert Joomla-Titel („doppelte Überschrift")
    h2_match = re.search(r"<h2>(.*?)</h2>", body, re.DOTALL)
    if h2_match:
        h2_text = h2_match.group(1).strip()
        errors.append(f"⛔ <h2> im Body (\"{h2_text[:60]}...\") — wird gestrippt (Joomla-Titel reicht)!")
        body = body[: h2_match.start()] + body[h2_match.end() :]

    # ⛔ STRIP: <p class="meta"> — Datum kommt von Joomla
    if re.search(r'<p class="meta">', body):
        errors.append(f"⛔ <p class=\"meta\"> im <article>-Body — wird gestrippt!")
        body = re.sub(r'<p class="meta">.*?</p>\s*', "", body, count=1, flags=re.DOTALL)

    # ⛔ STRIP: <p class="dachzeile"> — wird per extract_meta() vom Server geholt
    if re.search(r'<p class="dachzeile">', body):
        errors.append(f"⛔ <p class=\"dachzeile\"> im <article>-Body — wird gestrippt!")
        body = re.sub(r'<p class="dachzeile">.*?</p>\s*', "", body, count=1, flags=re.DOTALL)

    # ⛔ KEIN <br> nach Read-More
    if re.search(r'system-readmore"\s*/>\s*<br', body):
        errors.append(f"⛔ <br> nach Read-More — wird gestrippt!")
        body = re.sub(r'(system-readmore"\s*/>)\s*<br\s*/?>', r"\1", body)

    # ⛔ Prüfe: Vorbestell-Box (wenn vorhanden) MUSS im <article> sein
    # (Ist bereits, da wir nur <article> extrahiert haben — aber Check fürs Protokoll)
    has_vorbestell = bool(re.search(r'(?:vorbestell-box|border:\s*2px\s+dotted.*Vorbestellbar)', body, re.DOTALL | re.IGNORECASE))

    return body.strip(), errors


def validate_body(body: str) -> list[str]:
    """Validiere den bereinigten Body. Returns Liste von Fehlern."""
    errors = []

    # Read-More MUSS vorhanden sein
    if '<hr id="system-readmore"' not in body:
        errors.append("❌ KEIN Read-More-Tag (<hr id=\"system-readmore\" />)!")

    # Keine Reste von <title>, <h1>, meta, dachzeile
    for tag in ["<title>", "<h1>", '<p class="meta">', '<p class="dachzeile">']:
        if tag in body:
            errors.append(f"❌ Verbotenes Element im Body: {tag} (trotz Strip!)")

    # Kein <br> alleinstehend nach Read-More
    if re.search(r'system-readmore"\s*/>\s*<br', body):
        errors.append("❌ <br> nach Read-More!")

    # Kein <style>-Tag
    if "<style>" in body and "</style>" in body:
        errors.append("⚠️  <style>-Tag im Body (Joomla-Template liefert CSS)")

    return errors


def split_readmore(body: str) -> tuple[str, str]:
    """Splitte Body am Read-More in introtext + fulltext.
    Entfernt den <hr>-Tag aus beiden Teilen.
    """
    sr_match = re.search(r'<hr id="system-readmore"\s*/>', body)
    if sr_match:
        intro = body[: sr_match.start()].strip()
        full = body[sr_match.end() :].strip()
        return intro, full
    else:
        # Kein Read-More → alles als fulltext (introtext bleibt leer)
        return "", body.strip()


def create_article(
    title: str,
    introtext: str,
    fulltext: str,
    catid: int = 61,
    state: int = 0,
    access: int = 6,
    metadesc: str = "",
    metakey: str = "",
) -> int | None:
    """Erstelle Artikel per Joomla API POST. Returns article ID oder None."""
    payload = {
        "title": title,
        "alias": f"{re.sub(r'[^a-z0-9-]', '', title.lower().replace(' ', '-').replace('ä','ae').replace('ö','oe').replace('ü','ue').replace('ß','ss'))[:80]}-{int(__import__('time').time()) % 100000}",
        "introtext": introtext,
        "fulltext": fulltext,
        "catid": str(catid),
        "state": str(state),
        "access": str(access),
        "created_by": "560",
        "language": "de",
        "featured": "0",
    }
    if metadesc:
        payload["metadesc"] = metadesc
    if metakey:
        payload["metakey"] = metakey

    data = json.dumps(payload).encode()
    req = urllib.request.Request(API_BASE, data=data, headers=HEADERS, method="POST")
    try:
        resp = urllib.request.urlopen(req)
        result = json.loads(resp.read())
        return int(result["data"]["id"])
    except urllib.error.HTTPError as e:
        body = e.read().decode()[:500]
        print(f"  ❌ CREATE fehlgeschlagen: HTTP {e.code}\n  {body}", file=sys.stderr)
        return None


def verify_article(aid: int, expected_body: str) -> list[str]:
    """Verifiziere Artikel nach Publish. Returns Fehlerliste."""
    errors = []
    req = urllib.request.Request(f"{API_BASE}/{aid}", headers=HEADERS_GET)
    try:
        resp = urllib.request.urlopen(req)
        r = json.loads(resp.read())
        attrs = r["data"]["attributes"]
        text = attrs.get("text", "")
        state = attrs.get("state")
        catid = r["data"]["relationships"]["category"]["data"]["id"]

        # 1. Text-Länge prüfen (±20% Toleranz, Joomla fügt Zeilenumbrüche hinzu)
        expected_len = len(expected_body)
        actual_len = len(text)
        ratio = actual_len / max(expected_len, 1)
        if ratio < 0.8:
            errors.append(
                f"❌ TEXT ZU KURZ: {actual_len} vs erwartet {expected_len} ({ratio:.0%})"
            )
        elif ratio > 1.3:
            errors.append(
                f"⚠️  TEXT LÄNGER: {actual_len} vs erwartet {expected_len} ({ratio:.0%})"
            )

        # 2. Read-More wirkt: text muss beide Teile enthalten
        # Joomla speichert introtext+fulltext ohne <hr>-Tag im text-Feld
        if actual_len < 300:
            errors.append("❌ Text extrem kurz — Read-More hat möglicherweise Content abgeschnitten!")
        
        # 3. Intro-Content prüfen (erste ~800 Zeichen sollten NICHT leer sein)
        if len(text) > 0 and len(text[:500].strip()) < 50:
            errors.append("❌ Intro-Text leer — Read-More-Fehler!")

        # 3. Vorbestell-Box prüfen (falls erwartet)
        if "Vorbestellbar" in expected_body and "Vorbestellbar" not in text:
            errors.append("❌ VORBESTELL-BOX FEHLT im publizierten Artikel!")

        # 4. Verbotene Elemente prüfen
        if "<title>" in text:
            errors.append("❌ <title> im publizierten Content (doppelte Überschrift)!")
        if "<h1>" in text:
            errors.append("❌ <h1> im publizierten Content!")
        if '<p class="meta">' in text:
            errors.append("❌ <p class=\"meta\"> im publizierten Content!")

        return errors

    except urllib.error.HTTPError as e:
        return [f"❌ VERIFY fehlgeschlagen: HTTP {e.code}"]


def publish(html_path: str, catid: int = 61, state: int = 0, access: int = 6) -> int | None:
    """Komplette Publish-Pipeline: validate → create → verify.
    
    Returns: article ID oder None bei Fehler.
    """
    print(f"\n{'='*60}")
    print(f"📄 PUBLISH: {html_path}")
    print(f"{'='*60}")

    # ── PHASE 1: VALIDATE ──────────────────────────────
    print("\n🔍 Phase 1: VALIDATE")
    html = load_html(html_path)

    # Meta extrahieren
    title_match = re.search(r"<title>(.*?)</title>", html, re.DOTALL)
    title = title_match.group(1).strip() if title_match else "UNTITLED"
    print(f"  Title: {title[:80]}...")

    meta_desc_match = re.search(r'<meta name="description" content="([^"]*)"', html)
    metadesc = meta_desc_match.group(1) if meta_desc_match else ""
    
    meta_key_match = re.search(r'<meta name="keywords" content="([^"]*)"', html)
    metakey = meta_key_match.group(1) if meta_key_match else ""

    # Body extrahieren + strippen
    body, strip_errors = extract_article_body(html)
    if not body:
        print(f"  ❌ FATAL: Kein <article>-Body!")
        return None

    for err in strip_errors:
        print(f"  {err}")
    print(f"  Body-Länge: {len(body)} Zeichen")

    # Validieren
    validation_errors = validate_body(body)
    if validation_errors:
        for err in validation_errors:
            print(f"  {err}")
        print("\n  ❌ VALIDIERUNG FEHLGESCHLAGEN — ABBRUCH")
        return None
    print("  ✅ Validierung bestanden")

    # ── PHASE 2: PUBLISH ──────────────────────────────
    print("\n📤 Phase 2: PUBLISH")
    intro, full = split_readmore(body)
    print(f"  introtext: {len(intro)} Zeichen")
    print(f"  fulltext:  {len(full)} Zeichen")

    aid = create_article(
        title=title,
        introtext=intro,
        fulltext=full,
        catid=catid,
        state=state,
        access=access,
        metadesc=metadesc,
        metakey=metakey,
    )
    if not aid:
        print("  ❌ PUBLISH FEHLGESCHLAGEN")
        return None
    print(f"  ✅ Artikel #{aid} erstellt")

    # ── PHASE 3: VERIFY ──────────────────────────────
    print("\n🔬 Phase 3: VERIFY")
    verify_errors = verify_article(aid, body)
    if verify_errors:
        for err in verify_errors:
            print(f"  {err}")
        print(f"\n  ❌ VERIFIZIERUNG FEHLGESCHLAGEN — Artikel #{aid} hat Probleme!")
        print(f"  🔗 https://www.brettspiel-news.de/?view=article&id={aid}")
        return aid  # Artikel existiert, aber ist fehlerhaft
    print(f"  ✅ Alle Checks bestanden — Artikel #{aid} ist sauber")
    print(f"  🔗 https://www.brettspiel-news.de/?view=article&id={aid}")

    return aid


# ── CLI ───────────────────────────────────────────────────
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python3 publish_gatekeeper.py <artikel.html> [catid] [state] [access]")
        print("  catid:  61=Korrektur (default), 65=Reviews, 68=Deals, 52=Premium")
        print("  state:   0=unveröffentlicht (default), 1=veröffentlicht")
        print("  access:  6=Redaktion (default), 1=Public")
        sys.exit(1)

    html_file = sys.argv[1]
    catid = int(sys.argv[2]) if len(sys.argv) > 2 else 61
    state = int(sys.argv[3]) if len(sys.argv) > 3 else 0
    access = int(sys.argv[4]) if len(sys.argv) > 4 else 6

    result = publish(html_file, catid=catid, state=state, access=access)
    if result:
        print(f"\n✅ Fertig — Artikel #{result}")
    else:
        print(f"\n❌ Publish fehlgeschlagen")
        sys.exit(1)
