#!/usr/bin/env python3
"""flarum-post.py — post a markdown article to community.azcomputerguru.com (Flarum) by
converting it to Flarum's s9e TextFormatter XML and inserting into the Flarum MySQL DB over
SSH to the IX server. The canonical machinery behind the /forum-post skill.

GRADUATED 2026-06-15 from .claude/tmp scratch (the first test case of the scratch-graduation
pipeline — see .claude/docs/graduation-pipeline.md). Secrets that were HARDCODED in the
scratch original (IX root SSH password, Flarum DB password) now load from the SOPS vault at
runtime, so this file is safe to commit.

NEXT STEP (Mike's plans): genericize — take TITLE / CONTENT_MD / SLUG / tag_id as inputs
(skill Phase 1) instead of the hardcoded demo article below. The converter + insert flow are
already generic; only the CONTENT_MD/TITLE/SLUG block is example data.
"""
import os, re, subprocess
import paramiko

# ---- secrets from vault (never hardcode) ----------------------------------------------------
def _root():
    return os.environ.get("CLAUDETOOLS_ROOT") or os.path.abspath(
        os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))

def vault_get(path, field):
    r = _root()
    p = subprocess.run(["bash", os.path.join(r, ".claude", "scripts", "vault.sh"),
                        "get-field", path, field],
                       capture_output=True, text=True, timeout=30)
    v = (p.stdout or "").strip()
    if not v:
        raise SystemExit(f"[ERROR] vault_get {path} {field} failed: {(p.stderr or '').strip()[:200]}")
    return v

HOST = "172.16.3.10"
SSH_USER = "root"
SSH_PASS = vault_get("infrastructure/ix-server.sops.yaml", "credentials.password")
DB_PASS = vault_get("services/flarum-community.sops.yaml", "credentials.db_password")

# ---- DEMO article (replace with skill Phase 1 inputs when genericizing) ---------------------
CONTENT_MD = """\
We did a server-wide audit today and found 7 production WordPress sites with search indexing silently disabled. The sites looked completely normal to visitors. Google couldn't see any of them.

Here's the setting, why it gets left on, and how to audit a whole cPanel server at once.

## The Setting

In WordPress: **Settings -> Reading -> "Discourage search engines from indexing this site"**

When checked, WordPress adds `<meta name="robots" content="noindex,follow">` to every page and sets `blog_public` to `0` in `wp_options`. When unchecked, `blog_public = 1`. One row in one table; no other indicator anywhere on the site.

## The Fix

One SQL update per site:

```sql
UPDATE wp_options SET option_value = '1' WHERE option_name = 'blog_public';
```

After updating, verify by fetching the page source and confirming there's no `<meta name="robots" content="noindex">` in the `<head>`."""

TITLE = 'WordPress "Discourage Search Engines" Setting -- How 7 Production Sites Lost Their Indexing'
SLUG = "wordpress-discourage-search-engines-setting-how-7-production-sites-lost-their-indexing"
TAG_ID = 7  # How-Tos & Tips


# ---- markdown -> Flarum s9e TextFormatter XML -----------------------------------------------
def xml_escape(t):
    return t.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")


def inline_to_xml(text):
    result = ""
    i = 0
    while i < len(text):
        if text[i:i + 2] == "**":
            end = text.find("**", i + 2)
            if end != -1:
                result += "<STRONG><s>**</s>" + inline_to_xml(text[i + 2:end]) + "<e>**</e></STRONG>"
                i = end + 2
                continue
        if text[i] == "`":
            end = text.find("`", i + 1)
            if end != -1:
                result += "<C><s>`</s>" + xml_escape(text[i + 1:end]) + "<e>`</e></C>"
                i = end + 1
                continue
        if text[i] == "*" and text[i:i + 2] != "**":
            j = i + 1
            end = -1
            while j < len(text):
                if text[j] == "*" and text[j:j + 2] != "**":
                    end = j
                    break
                j += 1
            if end != -1:
                result += "<EM><s>*</s>" + xml_escape(text[i + 1:end]) + "<e>*</e></EM>"
                i = end + 1
                continue
        result += xml_escape(text[i])
        i += 1
    return result


def md_to_s9e(md):
    lines = md.split("\n")
    elements = []
    i = 0
    while i < len(lines):
        line = lines[i]
        if not line.strip():
            i += 1
            continue
        if line.startswith("## "):
            elements.append("<H2><s>## </s>" + inline_to_xml(line[3:]) + "</H2>")
            i += 1
        elif line.startswith("### "):
            elements.append("<H3><s>### </s>" + inline_to_xml(line[4:]) + "</H3>")
            i += 1
        elif line.startswith("- "):
            items = []
            while i < len(lines) and lines[i].startswith("- "):
                items.append("<LI><s>- </s>" + inline_to_xml(lines[i][2:]) + "</LI>")
                i += 1
            elements.append("<LIST>" + "\n".join(items) + "</LIST>")
        elif re.match(r"^\d+\. ", line):
            items = []
            while i < len(lines) and re.match(r"^\d+\. ", lines[i]):
                m = re.match(r"^(\d+)\. (.*)", lines[i])
                items.append("<LI><s>" + m.group(1) + ". </s>" + inline_to_xml(m.group(2)) + "</LI>")
                i += 1
            elements.append('<LIST type="decimal">' + "\n".join(items) + "</LIST>")
        elif line.startswith("```"):
            lang = line[3:].strip()
            code_lines = []
            i += 1
            while i < len(lines) and not lines[i].startswith("```"):
                code_lines.append(xml_escape(lines[i]))
                i += 1
            i += 1
            code_body = "\n".join(code_lines)
            if lang:
                elements.append(f'<CODE lang="{lang}"><s>```{lang}</s><i>\n</i>' + code_body + "\n<e>```</e></CODE>")
            else:
                elements.append("<CODE><s>```</s><i>\n</i>" + code_body + "\n<e>```</e></CODE>")
        else:
            para_lines = []
            while i < len(lines) and lines[i].strip():
                l = lines[i]
                if (l.startswith("## ") or l.startswith("### ") or l.startswith("- ")
                        or l.startswith("```") or re.match(r"^\d+\. ", l)):
                    break
                para_lines.append(l)
                i += 1
            elements.append("<p>" + inline_to_xml("\n".join(para_lines)) + "</p>")
    return "<r>" + "\n\n".join(elements) + "</r>"


xml_content = md_to_s9e(CONTENT_MD)
print(f"[INFO] XML length={len(xml_content)}")

php_template = """<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

$dsn = 'mysql:host=localhost;dbname=azcompu_flarum;charset=utf8mb4';
$pdo = new PDO($dsn, 'azcompu_flarum', '%%DB_PASS%%', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);

$user_id = 1;
$tag_id  = %%TAG_ID%%;
$title   = %%TITLE_JSON%%;
$slug    = '%%SLUG%%';
$now     = date('Y-m-d H:i:s');

$content = <<<'FLARUM_POST_XML_END'
%%XML_CONTENT%%
FLARUM_POST_XML_END;

$stmt = $pdo->prepare("INSERT INTO discussions (title, comment_count, post_number_index, created_at, user_id, slug, is_private, is_approved) VALUES (?, 1, 1, ?, ?, ?, 0, 1)");
$stmt->execute([$title, $now, $user_id, $slug]);
$disc_id = $pdo->lastInsertId();
echo "Discussion ID: $disc_id\\n";

$stmt = $pdo->prepare("INSERT INTO posts (discussion_id, number, created_at, user_id, type, content, is_private, is_approved) VALUES (?, 1, ?, ?, 'comment', ?, 0, 1)");
$stmt->execute([$disc_id, $now, $user_id, $content]);
$post_id = $pdo->lastInsertId();
echo "Post ID: $post_id\\n";

$pdo->prepare("UPDATE discussions SET first_post_id=?, last_post_id=?, last_posted_at=?, last_posted_user_id=?, last_post_number=1 WHERE id=?")->execute([$post_id, $post_id, $now, $user_id, $disc_id]);
$pdo->prepare("INSERT INTO discussion_tag (discussion_id, tag_id) VALUES (?, ?)")->execute([$disc_id, $tag_id]);

echo "Done! URL: https://community.azcomputerguru.com/d/$disc_id-$slug\\n";
"""

import json as _json
php_script = (php_template
              .replace("%%XML_CONTENT%%", xml_content)
              .replace("%%DB_PASS%%", DB_PASS)
              .replace("%%TAG_ID%%", str(TAG_ID))
              .replace("%%SLUG%%", SLUG)
              .replace("%%TITLE_JSON%%", _json.dumps(TITLE)))

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, username=SSH_USER, password=SSH_PASS, timeout=10)
print("[OK] SSH connected")

sftp = client.open_sftp()
with sftp.open("/tmp/flarum_post.php", "wb") as f:
    f.write(php_script.encode("utf-8"))
sftp.close()


def run_chan(cmd):
    chan = client.get_transport().open_session()
    chan.exec_command(cmd)
    chan.shutdown_write()
    out = b""
    while not chan.exit_status_ready():
        if chan.recv_ready():
            out += chan.recv(4096)
    while chan.recv_ready():
        out += chan.recv(4096)
    return out.decode("utf-8", errors="replace"), chan.recv_exit_status()


out, rc = run_chan("php -l /tmp/flarum_post.php 2>&1")
print(f"Syntax: {out.strip()}")
out, rc = run_chan("php /tmp/flarum_post.php 2>&1")
print(f"rc={rc}\n{out}")
run_chan("rm -f /tmp/flarum_post.php")
client.close()
