#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""DAILY IMAGE POSTING — cron 2026-10-08T18:00Z (= 9 Okt 2026 01:00 WIB)
Item index 68: `ss_carousel_seo` (brand `studiosoft`, source `studiosoft_carousel`)
-> Threads StudioSoft (`6a335b9ec5ff4ce3a3225c2a`) SAJA. FB/IG/LinkedIn dilewati
(aturan 15 Sep 2026: FB StudioSoft TIDAK boleh gambar; LinkedIn SS text-only;
IG tidak dipakai pipeline ini).

DESAIN: berkas asli `studiosoft_carousel/*.png` (19 Jul-1 Agu 2026) GAGAL standar
16 Sep 2026 (verifikasi visual `vision_analyze`): garis separator putus-putus,
bullet glyph kotak rusak, footer "Swipe <glyph rusak>", blok nempel ke atas +
void besar di bawah, teks #1E40AF (navy lama yang DILARANG). Aturan Bos:
"Gambar lama/arsip yang belum sesuai standar ini JANGAN diposting - render ulang
dulu". Deck di-render ulang di `studiosoft_carousel_fixed_seo/` pakai
`ss_carousel_lib.py` (bg #122B63, rata kiri, satu blok tengah, tanpa separator,
bullet titik, 1080x1350) oleh `ss_carousel_seo_render.py`. Isi teks dipertahankan;
"Anda" -> "kamu" (nada sosmed 5 Okt) dan em dash dibuang (pola AI).

SLOT: SS Threads pakai kartu gambar di **Selasa & Jumat 08:00 WIB** (`ss_cards_run.py`,
PRIME_DAYS + job harian teks mengisi 08:00 HANYA di hari non-Sel/Jum). Kartu terakhir
yang terkunci = 8 Des 2026 (item 20 seri `ss_threads_images`), jadi kartu berikutnya
di slot cadence yang sama = **11 Des 2026 08:00 WIB** (`2026-12-11T01:00:00.000Z`,
Jumat). Diverifikasi kosong di jendela 07:30-08:30 WIB (read-only probe API).

Caption: nada sosmed santai (perintah Bos 5 Okt) -> akun brand pakai "kami"/tanpa
subjek, pembaca "kamu"; link artikel SEO studiosoft.id (HTTP 200, belum pernah dipakai
akun SS); 0 hashtag; 0 mention internal; 0 CJK; pancingan diskusi di ekor; plus
**2 nested reply** (perintah job: "1 image (cover), nested 2 post"). reply <=500 char.
"""
import json
import os
import re
import subprocess
import sys
from datetime import datetime, timedelta, timezone

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

repliz_openssl_transport.install(verbose=False)

NL2 = chr(10) * 2
SS_T = ACC.STUDIOSOFT_THREADS

ITEM_ID = "ss_carousel_seo"
SRC = os.path.join(ROOT, "studiosoft_carousel_fixed_seo", "01_cover.png")
MEDIA_NAME = "ss_carousel_seo_01_cover.jpg"      # nama baru (hindari cache Repliz)
SLOT_UTC = "2026-12-11T01:00:00.000Z"            # 11 Des 2026 08:00 WIB (Jumat)
TITLE = "Kenapa Bisnis Kamu Perlu SEO?"
TOPIC = "StudioSoft card"                        # patuh seri kartu SS (15 char)
LINK = "https://studiosoft.id/jasa-seo-surabaya-paket-harga/"

CAPTION = (
    "SEO bukan cuma soal pasang iklan. Ini kerja terukur supaya website kamu "
    "muncul saat orang nyari produk atau jasa kamu di Google."
    + NL2 +
    "68% pengalaman online dimulai dari mesin pencari. Kalau bisnis kamu belum "
    "kelihatan di sana, pelanggannya lari ke tempat lain."
    + NL2 +
    "Panduan lengkap + 3 pilar SEO-nya ada di sini "
    "\U0001F447" + chr(10) + LINK
    + NL2 +
    "Bagian mana yang paling bikin kamu bingung: teknis, konten, atau backlink? "
    "Tulis di komentar ya."
)

REPLIES = [
    ("Tiga pilar yang kami kerjakan untuk klien: on-page (judul, meta, heading, "
     "isi), off-page (backlink & otoritas), dan technical (kecepatan, struktur "
     "data, sitemap, mobile-friendly)."
     + NL2 +
     "Kalau harus pilih satu dulu, mana yang paling kamu butuhkan sekarang?"),
    ("Satu catatan penting: SEO itu maraton, bukan sprint. Biasanya butuh 3-6 "
     "bulan sebelum hasilnya kelihatan stabil di halaman pertama."
     + NL2 +
     "Kamu tim yang sabar nunggu hasil organik, atau lebih milih iklan dulu "
     "buat jangka pendek?"),
]

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 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)
    return f"{IMG_BASE_URL.rstrip('/')}/openclaw-img/{MEDIA_NAME}", 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 fetch_ss(client):
    docs, p = [], 1
    while True:
        r = client.get_schedules(page=p, limit=100)
        docs.extend(r["docs"])
        if p >= (r.get("totalPages") or 1):
            break
        p += 1
    return [d for d in docs
            if (d.get("accountId") or (d.get("account") or {}).get("id")) == SS_T]


def wp_media_check():
    """Langkah 3 job: item ini tidak lewat WP (media server openclaw-img)."""
    m = json.load(open(os.path.join(ROOT, "wp_image_map.json")))
    key = "studiosoft_carousel/01_cover.png"
    print("WP MAP lookup:", key, "->",
          m.get(key, "tidak ada (normal: batch lama pakai server openclaw-img)"))


def main():
    assert os.path.exists(SRC), f"gambar tidak ada: {SRC}"
    wp_media_check()

    for label, txt in [("CAPTION", CAPTION)] + [(f"REPLY{i+1}", r) for i, r in enumerate(REPLIES)]:
        print(f"--- {label} ({len(txt)} char) ---")
        print(txt)
        print("--- repr:", repr(txt[:90]))
        assert len(txt) <= 500, f"{label} {len(txt)} char > 500 (Threads hard limit)"
        assert len(txt) <= 480, f"{label} {len(txt)} char > 480 (hindari elipsis)"
        assert NL2 in txt, f"{label} tanpa baris kosong ganda (newline asli)"
        assert "\\n" not in txt, f"{label} memuat backslash-n LITERAL"
        assert not CJK_RE.search(txt), f"{label} ada teks CJK"
        assert not re.search(r"@\w+", txt), f"{label} ada mention"
        assert "#" not in txt, f"{label} ada hashtag (seri kartu SS: 0 hashtag)"
    assert CTA_RE.search(CAPTION), "caption tanpa pancingan diskusi"
    assert LINK in CAPTION, "caption tanpa link studiosoft.id"
    assert len(TOPIC) <= 45, f"topic {len(TOPIC)} char (>45)"

    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("STOP: URL publik tidak 200 — jangan schedule.")
        return 1

    c = get_client()
    ss = fetch_ss(c)
    print("SS Threads schedules:", len(ss))

    # idempotensi: media ini sudah terjadwal?
    hits = [d for d in ss
            if any((m.get("url") or "").split("?")[0].rsplit("/", 1)[-1] == MEDIA_NAME
                   for m in (d.get("medias") or []))]
    if hits:
        print("SKIP: media sudah terjadwal:", [(d.get("id"), d.get("status"), d.get("scheduleAt")) for d in hits])
        return 0

    # slot harus kosong di jendela 07:30-08:30 WIB hari itu
    target = datetime.fromisoformat(SLOT_UTC.replace("Z", "+00:00")) + timedelta(hours=7)
    bentrok = []
    for d in ss:
        at = d.get("scheduleAt")
        if not at:
            continue
        wib = datetime.fromisoformat(at.replace("Z", "+00:00")) + timedelta(hours=7)
        if wib.date() == target.date() and "07:30" <= wib.strftime("%H:%M") <= "08:30":
            bentrok.append((d.get("id"), wib.strftime("%H:%M"), d.get("type")))
    print(f"SLOT {target.strftime('%Y-%m-%d %H:%M')} WIB bentrok?", bentrok or "TIDAK ✅")
    if bentrok:
        print("STOP: slot terisi.")
        return 1

    res = c.create_schedule(
        SS_T, CAPTION, SLOT_UTC,
        title=TITLE, topic=TOPIC, type_="image",
        medias=[{"url": url, "type": "image", "alt": "SEO untuk bisnis: kenapa penting"}],
        replies=[{"text": t, "username": ""} for t in REPLIES],
    )
    sid = res.get("scheduleId") or res.get("_id") or res.get("id")
    print("CREATE:", json.dumps(res)[:300])
    print("SID:", sid)
    if not sid:
        return 1

    # ── VERIFIKASI baca-balik ───────────────────────────────────────────────
    d = c.get_schedule(sid) or {}
    wib = datetime.fromisoformat((d.get("scheduleAt") or "").replace("Z", "+00:00")) + timedelta(hours=7)
    desc = d.get("description") or ""
    reps = d.get("replies") or []
    med = d.get("medias") or []
    print(f"VERIFIKASI: status={d.get('status')} type={d.get('type')} "
          f"at={wib.strftime('%Y-%m-%d %H:%M')} WIB topic={d.get('topic')!r} "
          f"desc={len(desc)} replies={len(reps)} medias={len(med)}")
    print("  media:", (med[0].get("url") if med else None))
    print("  newline_asli:", NL2 in desc, "literal_bsn:", chr(92) + "n" in desc)
    print("  akun:", (d.get("account") or {}).get("name"))
    ok = (d.get("status") in ("pending", "success") and d.get("type") == "image"
          and len(med) == 1 and med[0].get("url") == url and len(reps) == 2
          and NL2 in desc and len(desc) <= 500 and wib.strftime("%Y-%m-%d %H:%M") == "2026-12-11 08:00")
    if not ok:
        print("GAGAL VERIFIKASI — state tidak dimajukan.")
        return 1
    print("OK TERVERIFIKASI ✅")

    # ── 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))
    sudah = any(e.get("item_id") == ITEM_ID for e in log)
    if sudah:
        print("INFO: item sudah ada di log — state tidak di-increment ulang")
    else:
        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": "studiosoft", "source": "studiosoft_carousel",
            "filename": "01_cover.png (RE-RENDER: studiosoft_carousel_fixed_seo/01_cover.png)",
            "topic": TOPIC,
            "slot_note": "slot kartu SS: Selasa/Jumat 08:00 WIB — cadence seri ss_threads_images "
                         "berakhir 8 Des 2026, kartu berikutnya 11 Des 2026 08:00 WIB",
            "schedule_times": {"threads_ss": f"{SLOT_UTC} (2026-12-11 08:00 WIB) - image cover + 2 nested reply"},
            "repliz_ids": {"threads_ss": sid},
            "caption_chars": len(CAPTION),
            "media_url": url,
            "caption_final": CAPTION,
            "replies": REPLIES,
            "link": LINK,
            "design_note": "Aset lama di studiosoft_carousel/*.png GAGAL standar 16 Sep (separator "
                           "putus-putus, glyph rusak, #1E40AF, void besar) -> di-render ulang dgn "
                           "ss_carousel_lib.py (bg #122B63, rata kiri, blok tengah, bullet titik). "
                           "Deck baru: studiosoft_carousel_fixed_seo/ (render: ss_carousel_seo_render.py).",
            "next_index": st["index"],
            "hook_note": "netral (pertanyaan terbuka + ajak komentar, nada santai 5 Okt 2026)",
        })
        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())
