#!/usr/bin/env python3
"""DAILY IMAGE POSTING — cron 2026-09-16T18:00Z (= 17 Sep 2026 01:00 WIB)
Item index 46: uky_thread_16_blue_personal_branding.png (brand ukyhandoko) → Threads @ukyhandoko.

Tema gambar (hasil vision, kartu 6/6 seri @ukyhandoko):
Judul: "Dulu Saya Pikir Personal Branding Itu 'Norak'"
Isi:  "Saya kira cukup perusahaan aja yang branding. Ternyata di era digital, orang beli
      dari orang, bukan dari logo. Setelah mulai aktif di Threads & LinkedIn, klien baru
      datang bukan lewat website — tapi dari postingan yang mereka baca. Personal branding
      bukan gengsi. Ini investasi."
Footer: @ukyhandoko • Founder StudioSoft

Verifikasi aset: 1080x1080, bg RGB(18,43,99) = #122B63 (standar 16 Sep 2026), rata kiri,
tanpa garis separator, footer ada → LAYAK posting.

Aturan: Threads ≤500 char, gaya personal "saya", pancingan diskusi (diskusi_engine
industri=personal_branding, bobot 60/25/15), 0 hashtag, 0 mention internal, replies=[].
Slot: 08:00 WIB bebas PERTAMA (01:00Z) — dipilih lewat cek API, bukan catatan lama.
"""
import json, os, re, sys, subprocess, random
from datetime import datetime, timezone, timedelta

sys.path.insert(0, "/home/mantapman/.hermes/profiles/atun/workspace")
from repliz_client import get_client, ACC, IMG_BASE_URL, IMG_DIR
from PIL import Image
from diskusi_engine import ambil_pancingan, PANCINGAN

ROOT = "/home/mantapman/.hermes/profiles/atun/workspace"
ITEM_ID = "uky_thread_16_blue_personal_branding"
FILENAME = "16_blue_personal_branding.png"
SRC = os.path.join(ROOT, "uky_threads_images", FILENAME)
UKY_T = ACC.UKY_THREADS
TITLE = "Personal Branding Itu Investasi, Bukan Gengsi"
TOPIC = "Personal branding = aset: orang beli dari orang, bukan dari logo"
EXPECTED_BASENAME = FILENAME.rsplit(".", 1)[0].lower() + ".jpg"
SLOT_WIB_HOUR = 8  # slot target 08:00 WIB = 01:00Z

BODY = (
    "Dulu saya pikir personal branding itu 'norak'. Saya kira cukup perusahaan aja yang punya branding. \U0001F64C\n\n"
    "Ternyata di era digital orang beli dari orang, bukan dari logo. Setelah aktif di Threads & LinkedIn, "
    "klien baru datang bukan lewat website — tapi dari postingan yang mereka baca.\n\n"
    "Personal branding bukan gengsi. Ini investasi."
)

CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut|sepakat|tanggapan|ceritakan", re.I)


def used_hooks():
    """Semua hook (paragraf terakhir) yang sudah pernah dipakai — biar tidak berulang."""
    out = set()
    lp = os.path.join(ROOT, "image_posting_log.json")
    for e in json.load(open(lp)):
        cap = (e.get("caption_final") or "").strip()
        if cap:
            out.add(cap.split("\n\n")[-1].strip())
    return out


def build_caption():
    """Body + pancingan diskusi (bobot 60% netral / 25% opini / 15% sanjungan)."""
    exclude = used_hooks()
    for _ in range(60):
        tone = random.choices(["netral", "opini", "sanjungan"], weights=[60, 25, 15])[0]
        hook = ambil_pancingan(industri="personal_branding", tone=tone)
        cap = BODY + "\n\n" + hook
        if len(cap) <= 500 and CTA_RE.search(hook) and hook not in exclude:
            return cap, hook, tone
    kandidat = [s for t in ("netral", "opini", "sanjungan")
                for s in PANCINGAN["personal_branding"][t]
                if CTA_RE.search(s) and s not in exclude]
    for s in sorted(kandidat, key=len):
        if len(BODY) + len(s) + 2 <= 500:
            return BODY + "\n\n" + s, s, "fallback"
    raise SystemExit("❌ tidak ada pancingan yang muat / masih unik")


def upload():
    """PNG → JPEG di openclaw-img (standar desain 16 Sep), return URL publik."""
    os.makedirs(IMG_DIR, exist_ok=True)
    img = Image.open(SRC)
    if img.mode in ("RGBA", "LA", "P"):
        img = img.convert("RGB")
    out = os.path.join(IMG_DIR, FILENAME.rsplit(".", 1)[0] + ".jpg")
    img.save(out, format="JPEG", quality=90)
    url = f"{IMG_BASE_URL.rstrip('/')}/openclaw-img/{os.path.basename(out)}"
    return url, out


def check_public(url):
    """Simulasi jalur INTERNET (resolve ke ingress Tailscale) — bukan tailnet-direct."""
    codes = {}
    for ip in ("103.84.155.153", "103.84.155.217"):
        r = subprocess.run(
            ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "25",
             "--resolve", f"mantapserver.tail3947bb.ts.net:443:{ip}", url],
            capture_output=True, text=True)
        codes[ip] = r.stdout.strip()
    return codes


def all_docs(c):
    r = c.get_schedules(page=1, limit=100)
    pages = (r["totalDocs"] + 99) // 100
    docs = list(r["docs"])
    for p in range(2, pages + 1):
        docs.extend(c.get_schedules(page=p, limit=100)["docs"])
    return docs


def uky_pending(docs):
    out = []
    for d in docs:
        acc = d.get("account") or {}
        if (d.get("accountId") or acc.get("id") or acc.get("_id")) != UKY_T:
            continue
        if d.get("status") != "pending":
            continue
        out.append(d)
    out.sort(key=lambda d: d.get("scheduleAt") or "")
    return out


def first_free_8am(pending):
    """Slot 08:00 WIB (01:00Z) pertama yang kosong, mulai hari ini (WIB)."""
    today_wib = (datetime.now(timezone.utc) + timedelta(hours=7)).date()
    taken = {d["scheduleAt"][:10] for d in pending if (d.get("scheduleAt") or "")[11:13] == "01"}
    hari = today_wib
    for _ in range(120):
        if hari.isoformat() not in taken:
            return hari
        hari = hari + timedelta(days=1)
    raise SystemExit("❌ tidak ada slot 08:00 WIB bebas dalam 120 hari")


def main():
    # ── 0. validasi aset (standar desain 16 Sep) ──
    img = Image.open(SRC).convert("RGB")
    w, h = img.size
    bg = img.getpixel((5, 5))
    print(f"ASET: {SRC} {w}x{h} bg RGB{bg}")
    if bg != (18, 43, 99):
        print("❌ background bukan #122B63 (standar 16 Sep) — STOP, render ulang dulu.")
        return 1

    cap, hook, tone = build_caption()
    print(f"CAPTION ({len(cap)} chars) tone={tone}\n{cap}\n")
    assert len(cap) <= 500, f"caption {len(cap)} char > 500"
    assert not re.search(r"@\w+", cap), "ada mention!"
    assert CTA_RE.search(hook), "tidak ada pancingan diskusi"
    assert "#" not in cap, "ada hashtag (aturan: 0 hashtag untuk seri ini)"
    assert "..." not in cap, "ada elipsis truncate"

    url, out = upload()
    print("LOCAL JPEG:", out, os.path.getsize(out), "bytes")
    print("PUBLIC URL:", url)
    codes = check_public(url)
    print("INTERNET-PATH CODES:", codes)
    if not any(c == "200" for c in codes.values()):
        print("❌ URL publik tidak 200 — STOP, jangan schedule (aturan media server).")
        return 1

    c = get_client()
    docs = all_docs(c)
    pending = uky_pending(docs)

    # ── PILIH SLOT: 08:00 WIB bebas pertama (jangan percaya catatan log lama) ──
    hari = first_free_8am(pending)
    slot_dt = datetime(hari.year, hari.month, hari.day, SLOT_WIB_HOUR - 7, 0, 0, tzinfo=timezone.utc)
    SLOT_UTC = slot_dt.strftime("%Y-%m-%dT%H:%M:%S.000Z")
    slot_wib_str = f"{hari.isoformat()} {SLOT_WIB_HOUR:02d}:00 WIB"
    print("SLOT TERPILIH:", SLOT_UTC, f"({slot_wib_str})")

    def list_uky_media():
        """Selalu ambil ULANG dari API — jangan pakai snapshot lama."""
        hits = []
        for d in all_docs(c):
            acc = d.get("account") or {}
            if (d.get("accountId") or acc.get("id") or acc.get("_id")) != UKY_T:
                continue
            meds = d.get("medias") or []
            u = (meds[0].get("url") if meds else "") or ""
            # ⚠️ exact basename — substring match pernah salah cocokkan seri uky_quote_*
            if os.path.basename(u.split("?")[0]).lower() == EXPECTED_BASENAME:
                hits.append(d)
        return hits

    # ── IDEMPOTENSI (create response pakai 'scheduleId', bukan '_id') ──
    pre = list_uky_media()
    if pre:
        sid = pre[0].get("id")
        SLOT_UTC = pre[0].get("scheduleAt") or SLOT_UTC
        hari = (datetime.fromisoformat(SLOT_UTC.replace("Z", "+00:00")) + timedelta(hours=7)).date()
        slot_wib_str = f"{hari.isoformat()} {SLOT_WIB_HOUR:02d}:00 WIB"
        print(f"⏭️  sudah ada {len(pre)} jadwal utk media ini → SKIP create (id={sid})")
    else:
        res = c.create_schedule(
            UKY_T, cap, SLOT_UTC,
            title=TITLE, topic=TOPIC,
            type_="image",
            medias=[{"url": url, "type": "image", "alt": FILENAME}],
            replies=[],
        )
        sid = res.get("scheduleId") or res.get("_id") or res.get("id")
        print("CREATE RESPONSE:", json.dumps(res)[:400])
    print("SID:", sid)

    # ── VERIFIKASI via re-list (jangan percaya response create) ──
    hits = list_uky_media()
    print(f"\nVERIFIKASI: {len(hits)} jadwal {EXPECTED_BASENAME} di Threads uky")
    ok = False
    for d in hits:
        wib = datetime.fromisoformat(d["scheduleAt"].replace("Z", "+00:00")) + timedelta(hours=7)
        print(f"  id={d.get('id')} status={d.get('status')} type={d.get('type')} at={wib.strftime('%Y-%m-%d %H:%M')} WIB")
        print(f"  media={d['medias'][0]['url']}")
        print(f"  desc({len(d.get('description') or '')}) replies={len(d.get('replies') or [])}")
        if d.get("id") == sid and d.get("status") == "pending":
            ok = True
    if not (ok and len(hits) == 1):
        print("⚠️ verifikasi tidak pas (harus 1 pending dgn sid cocok) → state TIDAK dimajukan")
        return 1
    print("\n✅ TERVERIFIKASI: 1 jadwal pending, media + caption terpasang")

    # ── UPDATE STATE + LOG ──
    sp = os.path.join(ROOT, "image_posting_state.json")
    st = json.load(open(sp))
    st["index"] = (st["index"] + 1) % st["total"]
    json.dump(st, open(sp, "w"), indent=2)
    print("STATE →", st)

    lp = os.path.join(ROOT, "image_posting_log.json")
    log = json.load(open(lp))
    log.append({
        "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
        "item_id": ITEM_ID, "brand": "ukyhandoko", "source": "uky_threads_images",
        "filename": FILENAME, "topic": TOPIC,
        "slot_note": f"slot 08:00 WIB bebas pertama dari cek API = {slot_wib_str}",
        "schedule_times": {"threads_uky": f"{SLOT_UTC} (08:00 WIB {hari.isoformat()}) - image 1"},
        "repliz_ids": {"threads_uky": sid},
        "caption_chars": len(cap), "media_url": url,
        "caption_final": cap,
        "next_index": st["index"],
    })
    json.dump(log, open(lp, "w"), indent=2, ensure_ascii=False)
    print("LOG →", lp, f"({len(log)} entri)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
