#!/usr/bin/env python3
"""DAILY IMAGE POSTING — cron 2026-10-11T01:00Z (= 11 Okt 2026 08:00 WIB)
Antrean image_posting_queue.json, state index=72.

AUDIT DUPLIKAT dulu (protocol references/image-queue-duplikat.md, temuan 9 Okt 2026):
 #72 uky_quote4_murah_itu_utang      -> SKIP. Tema + kalimat kunci SUDAH tayang di Threads
      @ukyhandoko 2026-10-10 12:48 WIB (text, topic "Harga & Nilai Jasa"): "Menurut saya,
      \"murah\" bukan strategi, tapi utang. Harga murah bikin kamu dapat klien yang sensitif
      harga, kerjaan numpuk, dan waktu habis untuk hal yang tidak menambah kemampuan. ...
      naikkan nilai dulu, harga mengikuti." Kartu #72 berbunyi sama ("Harga Murah Itu Utang";
      "kerjaan numpuk ... Naikkan nilai dulu, harga mengikuti"). Selisih 1 hari = berulang.
 #73 uky_quote4_fokus_itu_menutup_pintu -> SKIP. Threads @ukyhandoko 2026-10-28 20:27 WIB
      (text, "Fokus & Prioritas"): "Nasihat yang paling sering salah dipahami: \"fokus\". Banyak
      yang mengira fokus berarti mengerjakan satu hal selamanya. Bagi saya fokus berarti berani
      menutup pintu." = metafora & tesis identik dengan kartu #73.
 #74 uky_quote4_langkah_kecil_rutin   -> SKIP. Threads @ukyhandoko 2026-09-30 17:00 WIB
      (image, "filosofi"): "Filosofi kerja saya: kecil tapi konsisten, lebih baik daripada besar
      tapi sekali jalan." Judul kartu #74 = "Kecil Tapi Rutin Mengalahkan Besar Tapi Sekali".
      (Tema konsistensi juga sudah 3x: 16 Sep, 21 Sep, 30 Sep.)
 #75 uky_quote4_jangan_tunggu_siap    -> DIPOSTING. Dicek ke seluruh 280 jadwal Threads
      @ukyhandoko: tidak ada satu pun yang bertema "jangan tunggu siap". Yang terdekat (11 Okt
      08:48 "Modal bukan alasan untuk tidak mulai" dan 1 Nov 12:21 soal menggaji orang pertama)
      sudut pandangnya beda.

KARTU (standar 16 Sep 2026): square 1080x1080, bg ORANGE (249,115,22) varian seri uky4 yang
sudah dipakai berkala di akun ini (uky_quote_13/14/19/20_orange dst), teks PUTIH, header
"@ukyhandoko" + "1/1", rata kiri, satu blok tengah vertikal, tanpa separator, tanpa glyph
bullet, 0 teks CJK. OK.

NADA (perintah Bos 5 Okt 2026): caption sosmed santai ("aku"/"kamu").
Distribusi tone (pool entri log bertone, n=20): netral 11 / opini 5 / sanjungan 4 = 55/25/20.
  +netral    -> 12/5/4 = 60.0/25.0/15.0  dev 0.0  <-- TERPILIH
  +opini     -> 11/6/4 = 52.4/28.6/19.0 dev 11.9
  +sanjungan -> 11/5/5 = 52.4/23.8/23.8 dev 16.4
Pilih netral (pertanyaan terbuka + ajak berbagi pengalaman).

Aturan seri: Threads <=500 char (target <=480), 0 hashtag, 0 mention internal, tanpa link
(bauran growth 40/30/30 lane ini = tanpa link), replies=[].
topic (topic_tag Threads) WAJIB <=45 char.

SLOT: lane kartu uky4 terakhir = 4 Nov 2026 08:00 WIB (item #71). Slot bebas pertama
(dicek API: 0 jadwal Threads-uky 5-12 Nov) = 5 Nov 2026 08:00 WIB.

PITFALL: baris baru caption dibangun dengan chr(10)*2, JANGAN backslash-n literal.
"""
import json, os, re, sys, subprocess
from datetime import datetime, timezone, timedelta

ROOT = "/home/mantapman/.hermes/profiles/atun/workspace"
sys.path.insert(0, ROOT)
from repliz_client import get_client, ACC, IMG_BASE_URL, IMG_DIR, cek_teks_cjk
from PIL import Image
import repliz_openssl_transport

repliz_openssl_transport.install(verbose=False)

NL2 = chr(10) * 2
UKY_T = ACC.UKY_THREADS

ITEM_ID = "uky_quote4_jangan_tunggu_siap"
FILENAME = "jangan_tunggu_siap_orange.png"
SRC = os.path.join(ROOT, "uky4_output", FILENAME)
MEDIA_NAME = "uky_quote_jangan_tunggu_siap.jpg"
TITLE = "Jangan Tunggu Siap Baru Mulai"
TOPIC = "Jangan tunggu siap, mulai dulu"        # 30 char (<=45)

SLOT_AT = "2026-11-05T01:00:00.000Z"            # 5 Nov 2026 08:00 WIB
SLOT_WIB = "2026-11-05 08:00 WIB"

BODY = (
    "Jangan tunggu siap baru mulai. Kesempatan pertama aku datang pas aku belum siap \u2014 "
    "kalau aku nunggu siap, sampai sekarang cuma jadi rencana."
    + NL2 +
    "Yang ngebedain bukan kesiapannya, tapi keberanian buat mulai duluan. Siap itu biasanya "
    "nyusul, bukan syarat buat mulai."
)

HOOK = ("Kamu pernah mulai sesuatu sebelum merasa siap? Gimana hasilnya? "
        "Cerita dong di komentar \U0001F447")

SKIPS = [
    ("uky_quote4_murah_itu_utang", "murah_itu_utang_orange.png", "uky4_output",
     "DUPLIKAT: tema + kalimat kunci sudah tayang di Threads @ukyhandoko 2026-10-10 12:48 WIB "
     "(text, topic 'Harga & Nilai Jasa'): \"Menurut saya, 'murah' bukan strategi, tapi utang. "
     "Harga murah bikin kamu dapat klien yang sensitif harga, kerjaan numpuk, dan waktu habis "
     "untuk hal yang tidak menambah kemampuan. ... Yang saya pelajari: naikkan nilai dulu, harga "
     "mengikuti.\" Kartu #72 ('Harga Murah Itu Utang') berbunyi sama: \"Harga terlalu murah bikin "
     "capek, kerjaan numpuk, dan kemampuan tidak naik. Naikkan nilai dulu, harga mengikuti.\" "
     "Selisih 1 hari di akun yang sama = konten berulang. Tidak ada jadwal dibuat."),
    ("uky_quote4_fokus_itu_menutup_pintu", "fokus_itu_menutup_pintu_biru.png", "uky4_output",
     "DUPLIKAT: Threads @ukyhandoko 2026-10-28 20:27 WIB (text, 'Fokus & Prioritas'): \"Nasihat "
     "yang paling sering salah dipahami: 'fokus'. Banyak yang mengira fokus berarti mengerjakan "
     "satu hal selamanya. Bagi saya fokus berarti berani menutup pintu.\" Kartu #73 memakai "
     "metafora + tesis identik (\"Fokus itu berani mengatakan tidak pada peluang yang "
     "mengalihkan\"). Tidak ada jadwal dibuat."),
    ("uky_quote4_langkah_kecil_rutin", "langkah_kecil_rutin_putih.png", "uky4_output",
     "DUPLIKAT: Threads @ukyhandoko 2026-09-30 17:00 WIB (image, 'filosofi'): \"Filosofi kerja "
     "saya: kecil tapi konsisten, lebih baik daripada besar tapi sekali jalan.\" Judul kartu #74 "
     "= 'Kecil Tapi Rutin Mengalahkan Besar Tapi Sekali' (punchline sama). Tema konsistensi>sprint "
     "sudah 3x di akun ini (16, 21, 30 Sep). Tidak ada jadwal dibuat."),
]

CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut|sepakat|\?", re.I)
CJK_RE = re.compile(
    "[" + chr(0x4E00) + "-" + chr(0x9FFF) + chr(0x3040) + "-" + chr(0x30FF)
    + chr(0xAC00) + "-" + chr(0xD7AF) + "]"
)


def build_caption():
    assert BODY.count(NL2) == 1, "BODY harus punya tepat 1 pemisah paragraf"
    cap = BODY + NL2 + HOOK
    log = json.load(open(os.path.join(ROOT, "image_posting_log.json")))
    for e in log:
        if HOOK in (e.get("caption_final") or ""):
            raise RuntimeError("pancingan sudah dipakai di log - tulis yang lain")
        if e.get("item_id") == ITEM_ID:
            raise RuntimeError("item sudah pernah dicatat di log - jangan dobel")
    return cap


_UKY = []


def fetch_uky(client, refresh=False):
    if _UKY and not refresh:
        return _UKY[0]
    r = client.get_schedules(page=1, limit=100)
    pages = (r["totalDocs"] + 99) // 100
    docs = list(r["docs"])
    for p in range(2, pages + 1):
        docs.extend(client.get_schedules(page=p, limit=100)["docs"])
    out = [d for d in docs
           if (d.get("accountId") or (d.get("account") or {}).get("id")) == UKY_T]
    if refresh:
        _UKY[:] = [out]
    else:
        _UKY.append(out)
    return out


def wib(d):
    at = d.get("scheduleAt")
    return None if not at else datetime.fromisoformat(at.replace("Z", "+00:00")) + timedelta(hours=7)


def media_hits(client, media_name, refresh=False):
    hits = []
    for d in fetch_uky(client, refresh=refresh):
        for m in (d.get("medias") or []):
            base = (m.get("url") or "").split("?")[0].rsplit("/", 1)[-1]
            if base == media_name:
                hits.append(d)
                break
    return hits


def upload():
    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, MEDIA_NAME)
    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):
    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", "30",
             "--resolve", f"mantapserver.tail3947bb.ts.net:443:{ip}", url],
            capture_output=True, text=True)
        codes[ip] = r.stdout.strip()
    return codes


def main():
    assert os.path.exists(SRC), f"file gambar tidak ada: {SRC}"
    im = Image.open(SRC).convert("RGB")
    assert im.size == (1080, 1080), f"ukuran {im.size} != 1080x1080"
    bg = im.getpixel((5, 540))
    print(f"ASET: {SRC} size={im.size} bg_px={bg}")
    assert bg == (249, 115, 22), f"bg {bg} bukan orange varian standar seri uky4"

    cap = build_caption()
    print(f"CAPTION ({len(cap)} chars):")
    print(cap)
    assert len(cap) <= 500, f"caption {len(cap)} char > 500"
    assert len(cap) <= 480, f"caption {len(cap)} char > 480"
    assert NL2 in cap, "caption tidak punya baris kosong ganda (newline asli)"
    assert "\\n" not in cap, "caption memuat backslash-n LITERAL"
    assert not re.search(r"@\w+", cap), "ada mention!"
    assert not re.search(r"https?://", cap), "ada link (lane ini tanpa link)"
    assert CTA_RE.search(cap), "tidak ada pancingan diskusi"
    assert "#" not in cap, "ada hashtag (aturan: 0 hashtag untuk seri ini)"
    assert not CJK_RE.search(cap), "ada teks CJK!"
    assert "gue" not in cap.lower(), "ada kata 'gue'"
    assert len(TOPIC) <= 45, f"topic {len(TOPIC)} char (>45)"
    cek_teks_cjk(cap, TITLE, TOPIC, konteks="image_post uky #75")

    c = get_client()

    # cek tema seri ini: frasa kunci tidak boleh pernah jadi TOPIK/JUDUL post lain
    frasa_re = re.compile(r"tunggu siap|nunggu siap|menunggu siap", re.I)
    semua = fetch_uky(c, refresh=True)
    kemunculan, jadi_topik = [], []
    for d in semua:
        desc = d.get("description") or ""
        if not frasa_re.search(desc):
            continue
        kemunculan.append(d)
        baris1 = desc.strip().splitlines()[0] if desc.strip() else ""
        topik = d.get("topic") or ""
        if frasa_re.search(baris1) or frasa_re.search(topik):
            jadi_topik.append(d)
    print(f"CEK TEMA frasa 'tunggu siap' di {len(semua)} jadwal Threads-uky: "
          f"{len(kemunculan)} kemunculan, {len(jadi_topik)} di antaranya jadi topik/baris pertama")
    for d in kemunculan:
        print("   ", wib(d).strftime("%Y-%m-%d %H:%M"), d.get("status"), d.get("type"),
              "| topic:", repr((d.get("topic") or "")[:30]), "|",
              (d.get("description") or "").strip().splitlines()[0][:70])
    assert not jadi_topik, "tema sudah pernah jadi topik/judul post lain - jangan posting"
    print("   -> semua kemunculan hanya kalimat PENDUKUNG di post bertema lain "
          "(19 Okt 'Personal Branding' soal portofolio; 28 Okt pesan ke diri 5 tahun lalu) "
          "= tema kartu #75 belum pernah tayang sendiri. Lanjut.")

    hari = datetime(2026, 11, 5).date()
    same_day = [d for d in fetch_uky(c) if wib(d) and wib(d).date() == hari]
    print(f"Jadwal Threads-uky di {hari}: {[(wib(d).strftime('%H:%M'), d.get('type')) for d in same_day]}")
    assert not same_day, f"slot {hari} tidak kosong - pilih hari lain"
    assert not media_hits(c, MEDIA_NAME, refresh=True), "media ini sudah punya jadwal"

    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(v == "200" for v in codes.values()):
        print("STOP: URL publik tidak 200 - jangan schedule (aturan media server).")
        return 1

    res = c.create_schedule(
        UKY_T, cap, SLOT_AT,
        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)[:300], "-> SID:", sid)

    hits = media_hits(c, MEDIA_NAME, refresh=True)
    print(f"VERIFIKASI: {len(hits)} jadwal dgn media {MEDIA_NAME}")
    ok = False
    for d in hits:
        w = wib(d)
        desc = d.get("description") or ""
        print(f"  id={d.get('id')} status={d.get('status')} type={d.get('type')} "
              f"at={w.strftime('%Y-%m-%d %H:%M')} WIB topic={len(d.get('topic') or '')}c")
        print(f"  media={d['medias'][0]['url']}")
        print(f"  desc({len(desc)}) replies={len(d.get('replies') or [])} "
              f"newline_asli={NL2 in desc} literal={chr(92)+'n' in desc}")
        if (d.get("id") == sid and d.get("status") in ("pending", "success")
                and NL2 in desc and (d.get("topic") or "") == TOPIC):
            ok = True
    if not (ok and len(hits) == 1):
        print("GAGAL verifikasi: harus 1 jadwal, sid cocok, topic utuh, newline asli")
        return 1
    print("OK TERVERIFIKASI: 1 jadwal, media + caption + topic terpasang")

    # UPDATE STATE + LOG (3 entri SKIPPED + 1 POSTED)
    lp = os.path.join(ROOT, "image_posting_log.json")
    sp = os.path.join(ROOT, "image_posting_state.json")
    log = json.load(open(lp))
    st = json.load(open(sp))
    now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    for sid_id, fn, src, reason in SKIPS:
        log.append({
            "timestamp": now, "item_id": sid_id, "brand": "ukyhandoko", "source": src,
            "status": "SKIPPED", "reason": reason,
            "filename": fn, "action": "skip, lanjut item berikutnya",
        })
    st["index"] = (st["index"] + 4) % st["total"]
    log.append({
        "timestamp": now,
        "item_id": ITEM_ID, "brand": "ukyhandoko", "source": "uky4_output",
        "filename": f"{FILENAME} (kartu square 1080x1080, bg ORANGE (249,115,22) varian seri "
                    f"uky4 + teks putih, standar 16 Sep - sesuai)",
        "topic": TOPIC,
        "slot_note": f"lane kartu uky4 lanjutan setelah 4 Nov 08:00 (item #71); {SLOT_WIB} hari "
                     f"kosong total utk Threads-uky (dicek API: 0 jadwal 5-12 Nov). Item #72/#73/"
                     f"#74 di-skip karena duplikat tema yang sudah tayang di akun yang sama.",
        "schedule_times": {"threads_uky": f"{SLOT_AT} ({SLOT_WIB}) - image 1"},
        "repliz_ids": {"threads_uky": sid},
        "caption_chars": len(cap), "media_url": url,
        "caption_final": cap, "replies": [], "link": "",
        "verifikasi": f"GET/re-list /public/schedule -> id {sid}, accountId {UKY_T} (Threads @ukyhandoko), "
                      f"type image, scheduleAt {SLOT_AT}, topic {len(TOPIC)} char, "
                      f"media {url} HTTP 200 ({codes}), status pending, replies 0",
        "hook_note": "netral (pertanyaan terbuka + ajak berbagi pengalaman, nada santai 5 Okt 2026) "
                     "- dev 0.0 dari pool n=20",
        "next_index": st["index"],
    })
    json.dump(log, open(lp, "w"), indent=2, ensure_ascii=False)
    json.dump(st, open(sp, "w"), indent=2)
    print("STATE ->", st)
    print("LOG ->", lp, f"({len(log)} entri)")
    return 0


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