#!/usr/bin/env python3
"""verify_hc40.py — verifikasi INDEPENDEN hasil create hc40 (fetch ulang per-ID dari API)."""
import json, os, re, subprocess, sys

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
sys.path.insert(0, WS)
from repliz_client import ReplizClient, ACC

IDS = {
    "threads": "6aad8bd19aa606c3bdc50bf9",
    "linkedin": "6aad8bd19aa606c3bdc50c01",
    "facebook": "6aad8bd1fc107e4fe5130f91",
}
EXPECT_MEDIA = ["ss_hc40_klinikkecil_01_cover.jpg", "ss_hc40_klinikkecil_02_mitos12.jpg",
                "ss_hc40_klinikkecil_03_mitos34.jpg", "ss_hc40_klinikkecil_04_langkah.jpg",
                "ss_hc40_klinikkecil_05_cta.jpg"]
CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut|bagaimana|apa yang", re.I)
INGRESS = ["103.84.155.153", "103.84.155.217"]
fails = []

c = ReplizClient()


def doc_of(sid):
    d = c.get("/public/schedule/%s" % sid)
    return d.get("doc") or d.get("data") or d


def check(cond, msg):
    print(("  \u2705 " if cond else "  \u274c ") + msg)
    if not cond:
        fails.append(msg)


def caption_rules(name, txt, limit=None):
    check(txt is not None and len(txt) > 0, "%s caption ada" % name)
    if limit:
        check(len(txt) <= limit, "%s caption %d char <= %d" % (name, len(txt), limit))
    check(not re.findall(r"@\w+", txt), "%s 0 mention" % name)
    caps = [t for t in re.findall(r"#(\w+)", txt) if t != t.lower()]
    check(not caps, "%s hashtag huruf kecil semua" % name)
    check(CTA_RE.search(txt[-200:]) is not None, "%s ada pancingan diskusi di ekor" % name)
    check("..." not in txt, "%s tidak ada elipsis" % name)


print("=== 1) THREADS album ===")
t = doc_of(IDS["threads"])
check(t.get("accountId") == ACC.STUDIOSOFT_THREADS, "accountId = SS Threads")
check(t.get("type") == "album", "type=album")
check(t.get("status") == "pending", "status=pending (%s)" % t.get("status"))
check(t.get("scheduleAt") == "2026-09-19T02:00:00.000Z", "scheduleAt=%s (19 Sep 09:00 WIB)" % t.get("scheduleAt"))
mnames = [os.path.basename((m.get("url") or "").split("?")[0]) for m in (t.get("medias") or [])]
check(mnames == EXPECT_MEDIA, "5 media urut 01-05: %s" % mnames)
check(len(t.get("replies") or []) == 0, "album replies kosong (replies dipasang di post teks)")
caption_rules("Threads", t.get("description") or "", limit=500)
check("https://studiosoft.id/klinik-kecil-butuh-digital-marketing/" in (t.get("description") or ""),
      "link artikel Threads ada")

print("=== 2) THREADS 2 replies (terpasang ke album) ===")
rows, page = [], 1
while True:
    d = c.get("/public/schedule?page=%d&limit=100" % page)
    rows.extend(d.get("docs", []))
    if page >= (d.get("totalPages") or 1):
        break
    page += 1
rep = []


def _norm_reply(x):
    """Repliz kadang balikin ObjectId sebagai dict char-index: {'0':'6','1':'a',...}."""
    if isinstance(x, dict):
        keys = [k for k in x if k.isdigit()]
        return "".join(str(x[k]) for k in sorted(keys, key=int))
    return str(x)


for r in rows:
    if r.get("accountId") != ACC.STUDIOSOFT_THREADS:
        continue
    reps = [_norm_reply(x) for x in (r.get("replies") or [])]
    if IDS["threads"] in reps and (r.get("scheduleAt") or "").startswith("2026-09-19T02:0") \
            and r.get("_id") != IDS["threads"]:
        rep.append(r)
check(len(rep) == 2, "2 reply menunjuk ke album (%d)" % len(rep))
for r in sorted(rep, key=lambda x: x.get("scheduleAt")):
    print("     %s | %s | %s" % (r.get("scheduleAt"), r.get("status"), (r.get("description") or "").split("\n")[0][:60]))

print("=== 3) LINKEDIN (TEXT-only) ===")
l = doc_of(IDS["linkedin"])
check(l.get("accountId") == ACC.STUDIOSOFT_LINKEDIN, "accountId = SS LinkedIn")
check(l.get("type") == "text", "type=text")
check(len(l.get("medias") or []) == 0, "medias=0 (tanpa gambar)")
check(len(l.get("replies") or []) == 0, "replies=0")
check(l.get("status") == "pending", "status=pending")
check(l.get("scheduleAt") == "2026-10-09T22:00:00.000Z", "scheduleAt=%s (10 Okt 05:00 WIB)" % l.get("scheduleAt"))
check(l.get("description", "").find("https://studiosoft.id/9-contoh-konten-digital-marketing-rumah-sakit-yang-edukatif-dan-tetap-etis/") >= 0,
      "link artikel LinkedIn ada")
caption_rules("LinkedIn", l.get("description") or "")

print("=== 4) FACEBOOK (TEXT-only, TANPA media) ===")
f = doc_of(IDS["facebook"])
check(f.get("accountId") == ACC.STUDIOSOFT_FB, "accountId = SS FB")
check(f.get("type") == "text", "type=text")
check(len(f.get("medias") or []) == 0, "medias=0 (tanpa gambar/carousel)")
check(len(f.get("replies") or []) == 0, "replies=[] WAJIB")
check(f.get("status") == "pending", "status=pending")
check(f.get("scheduleAt") == "2026-09-19T12:30:00.000Z", "scheduleAt=%s (19 Sep 19:30 WIB)" % f.get("scheduleAt"))
check("https://studiosoft.id/iklan-facebook-rumah-sakit/" in (f.get("description") or ""),
      "link artikel FB ada")
caption_rules("Facebook", f.get("description") or "")

print("=== 5) tidak ada media hc40 di IG/FB/LinkedIn SS ===")
def media_hits(aid):
    out = []
    for r in rows:
        if r.get("accountId") != aid:
            continue
        for m in (r.get("medias") or []):
            if "ss_hc40" in (m.get("url") or ""):
                out.append(((r.get("scheduleAt") or "")[:16], r.get("status")))
    return out


check(not media_hits(ACC.STUDIOSOFT_IG), "IG SS bersih dari media hc40 %s" % media_hits(ACC.STUDIOSOFT_IG))
check(not media_hits(ACC.STUDIOSOFT_FB), "FB SS bersih dari media hc40 %s" % media_hits(ACC.STUDIOSOFT_FB))
check(not media_hits(ACC.STUDIOSOFT_LINKEDIN), "LinkedIn SS bersih dari media hc40 %s" % media_hits(ACC.STUDIOSOFT_LINKEDIN))

print("=== 6) media masih 200 di KEDUA ingress (cek ulang) ===")
for f_ in EXPECT_MEDIA:
    u = "https://mantapserver.tail3947bb.ts.net/openclaw-img/" + f_
    codes = []
    for ip in INGRESS:
        r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "30",
                            "--resolve", "mantapserver.tail3947bb.ts.net:443:%s" % ip, u],
                           capture_output=True, text=True)
        codes.append(r.stdout.strip())
    check(codes == ["200", "200"], "%s -> %s" % (f_, codes))

print("=== 7) tidak ada post LAIN di slot carousel 09:00 WIB (02:00Z) ===")
slot = [r for r in rows if r.get("accountId") == ACC.STUDIOSOFT_THREADS
        and (r.get("scheduleAt") or "").startswith("2026-09-19T02:00")
        and (r.get("status") or "").lower() in ("pending", "scheduled", "draft")]
check(len(slot) == 1 and (slot[0].get("_id") or slot[0].get("id")) == IDS["threads"],
      "slot 02:00Z hanya diisi album hc40 (%d post)" % len(slot))
slots_today = sorted(set((r.get("scheduleAt") or "")[:13] for r in rows
                         if r.get("accountId") == ACC.STUDIOSOFT_THREADS
                         and (r.get("scheduleAt") or "").startswith("2026-09-19")
                         and (r.get("status") or "").lower() in ("pending", "scheduled", "draft")))
print("     slot Threads SS 19 Sep (pre-existing pipeline + hc40): %s" % slots_today)

print("\n" + "=" * 55)
print("SEMUA VERIFIKASI LULUS" if not fails else "GAGAL %d: %s" % (len(fails), fails))
sys.exit(0 if not fails else 1)
