#!/usr/bin/env python3
"""StudioSoft Healthcare — hc37 = RE-AIR hc20 "Pentingnya Konten Edukasi Kesehatan untuk Kepercayaan Pasien".

Cron run 16 Sep 2026 (02:00 WIB / 15 Sep 19:00 UTC).

Konteks:
- Stok artikel healthcare BARU sudah dipakai s/d hc36 (jasa-digital-marketing-klinik, 13 Sep).
  WP REST 15 Sep: 40 post teratas -> tidak ada artikel healthcare baru (terbaru 13 Sep).
- hc20 BELUM PERNAH tayang (2x error 'media could not be fetched' pada ss_hc20_*). Slide dibuat
  ULANG dengan nama file BARU ss_hc37_edukasi_* (jalur fetch Repliz segar) - sudah verified 200
  di KEDUA ingress + PIL.

ATURAN (FIXED 5 & 15 Sep 2026 - instruksi Bos):
- HANYA THREADS StudioSoft yang boleh carousel/image.
- Facebook StudioSoft: JANGAN image. (Keputusan isi FB text-only masih MENUNGGU jawaban Bos
  -> run ini FB tidak diberikan konten, lihat laporan.)
- LinkedIn StudioSoft: TEXT-only dari artikel (tanpa image), cerita + ajakan diskusi, replies=[].
- Instagram StudioSoft: tidak disentuh.
- Carousel = 09:00 WIB. Threads <=500 char. Hashtag huruf kecil. 0 mention. Wajib pancingan diskusi.
"""
import json, os, re, subprocess, sys
from datetime import datetime, timezone, timedelta

sys.path.insert(0, "/home/mantapman/.openclaw/workspace-atun")
from repliz_client import ReplizClient, ACC
from diskusi_engine import tambahkan_diskusi

DRY = os.environ.get("DRY", "") == "1"
WAT = "/home/mantapman/.openclaw/workspace-atun"
c = ReplizClient()

TITLE = "Pentingnya Konten Edukasi Kesehatan untuk Kepercayaan Pasien"
ARTICLE_URL = "https://studiosoft.id/konten-edukasi-kesehatan-kepercayaan-pasien"
HASHTAG_LINE = "#studiosoft #edukasikesehatan #digitalmarketing #rumahsakit #klinik"
LI_HASHTAG = "#studiosoft #edukasikesehatan #digitalmarketing #rumahsakit #klinik"

MEDIA_FILES = [
    "ss_hc37_edukasi_01_cover.jpg",
    "ss_hc37_edukasi_02_alasan.jpg",
    "ss_hc37_edukasi_03_ide.jpg",
    "ss_hc37_edukasi_04_platform.jpg",
    "ss_hc37_edukasi_05_cta.jpg",
]
MEDIA_URLS = ["https://mantapserver.tail3947bb.ts.net/openclaw-img/" + f for f in MEDIA_FILES]
IMG_DIR = WAT + "/openclaw-img"

THREADS_BASE = """Klinik yang rajin mengedukasi justru lebih dipercaya daripada yang rajin promo.

Bandingkan: "Promo medical checkup diskon 50%" vs "Kenali 5 tanda awal diabetes yang sering diabaikan". Yang kedua lebih mungkin di-share dan diingat.

Konten edukasi membangun otoritas, menaikkan engagement organik tanpa iklan besar, dan tidak melanggar etika promosi kesehatan."""

LI_BASE = """Dulu saya pikir cara paling cepat mendatangkan pasien adalah memperbanyak promo. Setelah ikut membedah konten RS dan klinik, saya berubah pikiran.

Yang benar-benar bekerja justru konten edukasi. Contoh sederhananya begini: "Promo medical checkup diskon 50%" versus "Kenali 5 tanda awal diabetes yang sering diabaikan". Yang kedua hampir selalu lebih banyak di-like, di-share, dan diingat. Bukan karena promonya salah, tapi karena edukasi menunjukkan bahwa kita peduli lebih dulu.

Ada alasan lain yang jarang disadari pemilik layanan kesehatan: artikel edukasi itu tahan lama. Ia bisa muncul di Google bertahun-tahun, sementara promo selesai dalam sepekan. Edukasi juga membangun otoritas, bisa di-repost pasien secara alami, dan tetap aman dari sisi etika promosi kesehatan.

Karena itu kami menyusun panduannya: mulai dari ide konten yang gampang dieksekusi — mitos vs fakta, tips kesehatan musiman, FAQ pasien, infografis prosedur — sampai platform yang paling efektif untuk masing-masing jenis konten. Instagram dan Facebook untuk visual pendek, YouTube untuk penjelasan dokter, website untuk artikel SEO.

Pelajaran yang saya ambil: edukasi dulu, promosi kemudian. Pasien yang melihat kita sebagai sumber informasi yang bermanfaat akan jauh lebih mudah memutuskan datang.

Bacaan lengkapnya: """ + ARTICLE_URL

CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut", re.I)
BAN_RE = re.compile(r"kecantikan|skincare|kulit|jerawat", re.I)
CURATED = [
    "Menurut Anda, konten edukasi seperti apa yang paling dipercaya calon pasien? Yuk diskusi di komentar.",
    "Menurut Anda, edukasi atau promo yang lebih efektif untuk layanan kesehatan? Share pengalaman Anda di komentar.",
]


def build(base, platform, industri, tones, limit=None, ban=None, max_try=80):
    for tone in tones:
        for _ in range(max_try):
            txt = tambahkan_diskusi(base, platform=platform, industri=industri, tone=tone, force=True)
            if not CTA_RE.search(txt[-200:]):        # ajakan diskusi WAJIB ada di ekor caption (hook)
                continue
            if limit is not None and len(txt) > limit:
                continue
            if ban is not None and ban.search(txt):
                continue
            if "..." in txt:
                continue
            return txt
    return None


def build_safe(base, platform, industri, tones, limit=None, ban=None):
    txt = build(base, platform, industri, tones, limit=limit, ban=ban)
    if txt is not None:
        return txt
    for hook in sorted(CURATED, key=len):
        cand = base.rstrip() + "\n\n" + hook
        if CTA_RE.search(cand) and (limit is None or len(cand) <= limit):
            return cand
    return base.rstrip() + "\n\n" + CURATED[0]


THREADS = build_safe(THREADS_BASE, "threads", "rumah_sakit", ["netral"], limit=500, ban=BAN_RE)
LI = build_safe(LI_BASE, "linkedin", "rumah_sakit", ["netral", "opini"], ban=BAN_RE)

REPLY1 = ("Baca selengkapnya: %s\n\nButuh strategi konten edukasi untuk RS & klinik Anda?\n"
          "www.studiosoft.id" % ARTICLE_URL)
REPLY2 = HASHTAG_LINE


def check_caption(name, txt, limit=None, require_cta=True):
    ok = True
    if txt is None:
        print("  ❌ %s: gagal disusun" % name)
        return False
    if limit and len(txt) > limit:
        print("  ❌ %s: %d char > %d" % (name, len(txt), limit))
        ok = False
    m = re.findall(r"@\w+", txt)
    if m:
        print("  ❌ %s: ada mention %s" % (name, m))
        ok = False
    caps = [t for t in re.findall(r"#(\w+)", txt) if t != t.lower()]
    if caps:
        print("  ❌ %s: hashtag KAPITAL %s" % (name, caps))
        ok = False
    if require_cta and not CTA_RE.search(txt):
        print("  ❌ %s: tidak ada pancingan diskusi" % name)
        ok = False
    print("  %s %s: %d char" % ("✅" if ok else "⚠️", name, len(txt)))
    return ok


def all_docs():
    docs, page = [], 1
    while True:
        d = c.get("/public/schedule?page=%d&limit=100" % page)
        docs.extend(d.get("docs", []))
        if page >= (d.get("totalPages") or 1):
            break
        page += 1
    return docs


def scan_media(statuses=("pending", "scheduled", "draft")):
    """FETCH ULANG dari API setiap dipanggil (jangan pakai snapshot lama)."""
    hits = []
    for s in all_docs():
        if s.get("accountId") not in (ACC.STUDIOSOFT_THREADS, ACC.STUDIOSOFT_LINKEDIN,
                                      ACC.STUDIOSOFT_FB, ACC.STUDIOSOFT_IG):
            continue
        if (s.get("status") or "").lower() not in statuses:
            continue
        for m in (s.get("medias") or []):
            base = os.path.basename((m.get("url") or "").split("?")[0]).lower()
            if base in [f.lower() for f in MEDIA_FILES]:
                hits.append((s.get("accountId"), (s.get("scheduleAt") or "")[:16],
                             (s.get("title") or "")[:40], s.get("type"), s.get("status")))
                break
    return hits


def busy_by_account(aid):
    busy = set()
    for s in all_docs():
        if s.get("accountId") == aid and (s.get("status") or "").lower() in ("pending", "scheduled", "draft"):
            busy.add((s.get("scheduleAt") or "")[:19])
    return busy


def next_free_09():
    """Slot 09:00 WIB (02:00Z) bebas pertama utk Threads SS, dihitung DARI API."""
    busy = busy_by_account(ACC.STUDIOSOFT_THREADS)
    now = datetime.now(timezone.utc)
    start = datetime(now.year, now.month, now.day, tzinfo=timezone.utc)
    if now.hour >= 2:            # slot 02:00Z hari ini sudah lewat
        start += timedelta(days=1)
    for day in range(0, 20):
        cand = (start + timedelta(days=day)).strftime("%Y-%m-%dT02:00:00")
        if cand not in busy:
            lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y 09:00 WIB")
            return cand + ".000Z", lab
    return None, None


def next_free_li():
    """7 slot WIB LI kosong pertama (dihitung dari API). Slot yang sudah lewat DILEWATI."""
    SLOTS = [("05:00 WIB", 22, -1), ("08:00 WIB", 1, 0), ("11:00 WIB", 4, 0), ("14:00 WIB", 7, 0),
             ("17:00 WIB", 10, 0), ("20:00 WIB", 13, 0), ("23:00 WIB", 16, 0)]
    busy = busy_by_account(ACC.STUDIOSOFT_LINKEDIN)
    now = datetime.now(timezone.utc) + timedelta(minutes=15)
    now_naive = now.replace(tzinfo=None)
    today = datetime.now(timezone.utc)
    for day in range(0, 45):
        for label, uh, shift in SLOTS:
            d0 = datetime(today.year, today.month, today.day, tzinfo=timezone.utc) + timedelta(days=day + shift)
            cand = "%sT%02d:00:00" % (d0.strftime("%Y-%m-%d"), uh)
            if datetime.fromisoformat(cand) <= now_naive:   # slot sudah lewat -> skip
                continue
            if cand not in busy:
                lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y %H:%M WIB")
                return cand + ".000Z", lab
    return None, None


def pil_ok():
    from PIL import Image
    ok = True
    for f in MEDIA_FILES:
        try:
            Image.open(os.path.join(IMG_DIR, f)).verify()
            im = Image.open(os.path.join(IMG_DIR, f))
            print("  ✅ %s %s %s" % (f, im.size, im.mode))
        except Exception as e:
            print("  ❌ %s: %s" % (f, e))
            ok = False
    return ok


def ingress_ips():
    ips = subprocess.run(["dig", "+short", "mantapserver.tail3947bb.ts.net", "A", "@1.1.1.1"],
                         capture_output=True, text=True).stdout.split()
    return [i for i in ips if i.count(".") == 3]


def verify_urls_internet():
    ips = ingress_ips()
    ok = True
    for u in MEDIA_URLS:
        for ip in ips[:2]:
            r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "25",
                                "--resolve", "mantapserver.tail3947bb.ts.net:443:%s" % ip, u],
                               capture_output=True, text=True)
            code = r.stdout.strip()
            print("  %s @%s: %s" % (u.split("/")[-1], ip, code))
            if code != "200":
                ok = False
    return ok


def verify_article():
    r = subprocess.run(["curl", "-sIL", "--http1.1", "--max-time", "200",
                        "-A", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", ARTICLE_URL,
                        "-o", "/dev/null", "-w", "%{http_code}"], capture_output=True, text=True)
    code = (r.stdout or "").strip()
    print("  langsung: %s" % code)
    if code != "200":
        r2 = subprocess.run(["curl", "-s", "--max-time", "120", "-o", "/dev/null", "-w", "%{http_code}",
                             "https://r.jina.ai/" + ARTICLE_URL], capture_output=True, text=True)
        code = (r2.stdout or "").strip()
        print("  via r.jina.ai: %s" % code)
    return code == "200"


def main():
    wib = datetime.now(timezone(timedelta(hours=7)))
    print("Time WIB: %s" % wib.strftime("%Y-%m-%d %H:%M"))

    print("\n1) SLOT:")
    SLOT_UTC, SLOT_LABEL = next_free_09()
    if not SLOT_UTC:
        print("  ❌ tidak ada slot 09:00 WIB bebas — batal.")
        return False
    print("  ✅ carousel Threads: %s (%s)" % (SLOT_LABEL, SLOT_UTC))
    LI_SLOT_UTC, LI_SLOT_LABEL = next_free_li()
    print("  ✅ LinkedIn text: %s" % (LI_SLOT_LABEL,))

    print("\n2) CAPTION:")
    ok = True
    ok &= check_caption("Threads", THREADS, 500)
    ok &= check_caption("LinkedIn", LI)
    ok &= check_caption("Reply1", REPLY1, require_cta=False)
    ok &= check_caption("Reply2", REPLY2, require_cta=False)
    if not ok:
        print("\n❌ caption gagal aturan — batal.")
        return False
    print("\n--- THREADS (%d char) ---\n%s" % (len(THREADS), THREADS))

    print("\n3) IDEMPOTENSI (media hc37 sudah pernah dijadwalkan?):")
    hits = scan_media(("pending", "scheduled", "draft", "success"))
    if hits:
        print("  ⚠️ sudah ada jadwal dgn media hc37 -> SKIP create:")
        for h in hits:
            print("   ", h)
        return True
    print("  ✅ belum ada")

    print("\n4) FILE JPEG LOKAL:")
    if not pil_ok():
        print("  ❌ media rusak — batal.")
        return False

    print("\n5) MEDIA JALUR INTERNET (2 ingress):")
    if not verify_urls_internet():
        print("  ❌ ada media tidak 200 — batal.")
        return False

    print("\n6) LINK ARTIKEL:")
    if not verify_article():
        print("  ❌ link tidak 200 — batal.")
        return False

    if DRY:
        print("\n[DRY RUN] semua cek lolos; tidak ada create.")
        return True

    base_dt = datetime.fromisoformat(SLOT_UTC.replace("Z", "+00:00"))
    r2 = (base_dt + timedelta(minutes=1)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    r3 = (base_dt + timedelta(minutes=2)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    medias = [c.make_media(u, "image", "slide%d" % (i + 1)) for i, u in enumerate(MEDIA_URLS)]

    results = {}
    print("\n7) CREATE — THREADS (album + 2 replies):")
    try:
        alb = c.schedule_album(ACC.STUDIOSOFT_THREADS, THREADS, SLOT_UTC, medias, title=TITLE)
        pid = alb.get("scheduleId") or alb.get("_id") or alb.get("id")
        print("  album -> %s" % pid)
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY1, r2, type_="text", replies=[pid])
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY2, r3, type_="text", replies=[pid])
        print("  2 replies OK")
        results["threads"] = True
    except Exception as e:
        print("  GAGAL %s" % e)
        results["threads"] = False

    print("\n   LINKEDIN (text-only, %s):" % LI_SLOT_LABEL)
    try:
        res = c.create_schedule(ACC.STUDIOSOFT_LINKEDIN, LI, LI_SLOT_UTC, type_="text", replies=[])
        pid = res.get("scheduleId") or res.get("_id") or res.get("id")
        print("  text -> %s" % pid)
        results["linkedin"] = True
    except Exception as e:
        print("  GAGAL %s" % e)
        results["linkedin"] = False

    print("\n" + "=" * 55)
    ok = all(results.values())
    print("SEMUA PLATFORM TERJADWAL" if ok else "GAGAL: %s" % [k for k, v in results.items() if not v])

    if ok:
        print("\n8) VERIFIKASI (fetch ULANG dari API):")
        for h in scan_media(("pending",)):
            print("   ", h)
        for s in all_docs():
            if s.get("accountId") == ACC.STUDIOSOFT_LINKEDIN and (s.get("status") or "").lower() in ("pending", "scheduled"):
                if ARTICLE_URL.rstrip("/") in (s.get("description") or ""):
                    print("    LI:", s.get("scheduleAt"), s.get("type"), "replies=%d" % len(s.get("replies") or []))

        st = json.loads(open(WAT + "/studiosoft_healthcare_state.json").read())
        st["index"] = st.get("index", 32) + 1
        st["topic"] = TITLE
        st["last_post"] = wib.strftime("%Y-%m-%d %H:%M")
        json.dump(st, open(WAT + "/studiosoft_healthcare_state.json", "w"))
        with open(WAT + "/studiosoft_healthcare_log.txt", "a") as f:
            f.write("\n[%s WIB] hc37 (RE-AIR hc20, slide BARU) | %s | %s | Threads album %s (%s) + 2 replies | "
                    "LinkedIn TEXT %s (%s) | FB: TIDAK diberi konten (aturan 15 Sep: tanpa gambar; keputusan "
                    "text-only FB masih menunggu Bos) | catatan: stok artikel healthcare baru HABIS (WP REST 40 post "
                    "teratas 15 Sep: terbaru 13 Sep, sdh dipakai hc36); hc20 belum pernah tayang (2x error media-fetch "
                    "pada ss_hc20_*) -> slide dibuat ULANG dgn nama file baru ss_hc37_edukasi_* (200 di KEDUA ingress + "
                    "PIL, 101-137 KB); state index -> %s.\n"
                    % (wib.strftime("%Y-%m-%d %H:%M"), TITLE, ARTICLE_URL, SLOT_LABEL,
                       SLOT_UTC[11:16] + "Z", LI_SLOT_LABEL, LI_SLOT_UTC, st["index"]))
        print("Log + state ditulis.")
    return ok


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