#!/usr/bin/env python3
"""DAILY IMAGE POSTING — cron 2026-10-10T18:00Z (= 11 Okt 2026 01:00 WIB)
Item index 71: uky_quote4_klien_mitra_bukan_raja -> uky4_output/klien_mitra_bukan_raja_putih.png
(brand ukyhandoko, source uky4_output) -> Threads @ukyhandoko saja.

KARTU (vision_analyze + PIL, standar 16 Sep 2026): square 1080x1080, bg PUTIH (255,255,255)
+ teks #122B63 (varian putih = versi bergantian yang diizinkan), header "@ukyhandoko" + "1/1",
judul "Klien Itu Mitra, Bukan Raja", body "Klien yang baik bukan yang selalu benar, tapi yang
mau diajak berpikir. Saya lebih memilih sedikit klien seperti itu.", footer "@ukyhandoko -
Founder StudioSoft". Rata kiri, satu blok di tengah vertikal, tanpa garis separator, tanpa
glyph bullet, 0 teks CJK. OK.

NADA (perintah Bos 5 Okt 2026): caption sosmed santai ("aku"/"kamu"), bukan bahasa brosur.
Kartu memakai kata "Saya" (naskah kartu) -> caption tetap santai.

Distribusi tone. Pool = semua entri log yang punya hook_note bertone terklasifikasi
(entri #49..#67, n=17): netral 9 / opini 4 / sanjungan 4 = 52.9/23.5/23.5
(karena 4 entri terakhir sudah saya/opini, target 60/25/15):
  +netral    -> 55.6/22.2/22.2  dev 14.4   <-- TERKECIL
  +opini     -> 52.9/29.4/23.5  dev 20.0
  +sanjungan -> 52.9/23.5/29.4  dev 23.0
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 untuk lane ini = tanpa link), replies=[].
topic (dikirim Repliz sebagai topic_tag Threads) WAJIB <=45 char.

SLOT (aturan job "1 konten/hari/akun" + convention 08:00 WIB):
 - Lane kartu Threads-uky terakhir = 3 Nov 2026 08:00 WIB (uky_quote_ai_alat_bukan_otak,
   item #70). Kartu berikutnya = hari bebas berikutnya: 4 Nov 2026 08:00 WIB (dicek API:
   tidak ada jadwal Threads-uky sama sekali di 4 Nov, apalagi di jendela 07:30-08:30 WIB).
 - Slot 12 Okt 08:00 WIB DILEWATI walau jendelanya kosong: 12 Okt 09:28 WIB sudah ada kartu
   sekeluarga tema klien (`19_orange_klien_ideal` "Karakter klien ideal"). Dua kartu tema klien
   di hari yang sama = konten terasa berulang (aturan anti-redundant Threads).

PITFALL: baris baru caption dibangun dengan chr(10)*2 (NL asli), JANGAN backslash-n literal.
Kalau caption sudah ada di log (hook sama) -> batal (jangan konten berulang).
"""
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
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_klien_mitra_bukan_raja"
FILENAME = "klien_mitra_bukan_raja_putih.png"
SRC = os.path.join(ROOT, "uky4_output", FILENAME)
MEDIA_NAME = "uky_quote_klien_mitra_bukan_raja.jpg"
TITLE = "Klien Itu Mitra, Bukan Raja"
TOPIC = "Klien itu mitra, bukan raja"          # 28 char (<=45)

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

BODY = (
    "Klien itu mitra, bukan raja. Dulu aku pikir klien selalu benar \u2014 sampai "
    "sadar itu bikin kerja sama jadi searah."
    + NL2 +
    "Yang aku cari sekarang justru klien yang mau diajak berpikir: mau dikritik, "
    "mau dibantah kalau ada yang lebih baik buat bisnisnya. Lebih sedikit, tapi "
    "hasilnya jauh lebih enak dilihat."
)

HOOK = ("Klienmu tipe yang mau diajak mikir, atau yang cuma mau dituruti? "
        "Cerita dong di komentar \U0001F447")

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 = []
    for d in docs:
        acc = d.get("account") or {}
        if (d.get("accountId") or acc.get("id") or acc.get("_id")) != UKY_T:
            continue
        out.append(d)
    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 wp_media_check():
    m = json.load(open(os.path.join(ROOT, "wp_image_map.json")))
    key = f"uky4_output/{FILENAME}"
    ent = m.get(key)
    if not ent:
        print("INFO: tidak ada di wp_image_map.json - normal untuk seri uky (tanpa WP):", key)
        return None
    print(f"WP MAP: {key} -> id={ent['id']} {ent['url']}")
    return ent


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 == (255, 255, 255), f"bg {bg} bukan putih (varian putih standar 16 Sep)"
    wp_media_check()

    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 (hindari elipsis truncate)"
    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' (aturan: sopan, tanpa gue)"
    assert len(TOPIC) <= 45, f"topic {len(TOPIC)} char (>45)"

    c = get_client()

    # cek slot bebas: tidak ada jadwal Threads-uky apa pun di hari itu
    hari = datetime(2026, 11, 4).date()
    same_day = [d for d in fetch_uky(c, refresh=True) 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 lagi - 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)

    # VERIFIKASI baca-balik (jangan percaya response create)
    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
    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))
    st["index"] = (st["index"] + 1) % st["total"]
    json.dump(st, open(sp, "w"), indent=2)
    log.append({
        "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
        "item_id": ITEM_ID, "brand": "ukyhandoko", "source": "uky4_output",
        "filename": f"{FILENAME} (kartu square 1080x1080, bg PUTIH + teks #122B63, standar 16 Sep - sesuai)",
        "topic": TOPIC,
        "slot_note": f"lane kartu uky4 lanjutan setelah 3 Nov 08:00 (item #70); {SLOT_WIB} "
                     f"hari kosong total utk Threads-uky. 12 Okt 08:00 dilewati (sudah ada kartu "
                     f"tema klien 09:28 di hari 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 minimum 14.4 dari pool n=17",
        "next_index": st["index"],
    })
    json.dump(log, open(lp, "w"), indent=2, ensure_ascii=False)
    print("STATE ->", st)
    print("LOG ->", lp, f"({len(log)} entri)")
    return 0


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