#!/usr/bin/env python3
"""
Scheduler konten ATTRACTIVE untuk Threads @ukyhandoko
======================================================
Aturan FIXED (8 Agustus 2026 — perintah Bos):
1. Image/carousel ke Threads WAJIB JPG via URL (BUKAN base64).
2. Carousel WAJIB tipe 'album' di Repliz.
3. Perbanyak konten: image, carousel, dan artikel dari web
   StudioSoft (studiosoft.id), Abimata (abimataconsultant.com),
   DMark (dmark.id), Abimata (abimataconsultant.com).
4. ANTI-REDUNDANT: semua konten dicatat di state file,
   TIDAK PERNAH diposting ulang (beda dari sebelumnya).
5. Caption max 500 karakter, Bahasa Indonesia humanis, pakai "saya".

Cara pakai:
  python3 uky_attractive_scheduler.py            # jadwalkan 3 konten berikutnya
  python3 uky_attractive_scheduler.py --slots 6  # jadwalkan 6 konten
  python3 uky_attractive_scheduler.py --list     # lihat pool & state
"""
import hashlib
import json
import os
import random
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone

from repliz_client import ReplizClient, ACC

WORK = os.path.expanduser("~/.openclaw/workspace-atun")
STATE_FILE = os.path.join(WORK, ".uky_attractive_state.json")
LOG_FILE = os.path.join(WORK, "uky_attractive_log.txt")

SLOT_HOURS = [5, 8, 11, 14, 17, 20, 23]  # WIB
WIB = timezone(timedelta(hours=7))

WEBS = {
    "studiosoft": "https://studiosoft.id",
    "dmark": "https://dmark.id",
    "abimata": "https://www.abimataconsultant.com",
}

# ───────────────────────── STATE ─────────────────────────

def load_state():
    if os.path.exists(STATE_FILE):
        try:
            with open(STATE_FILE) as f:
                return json.load(f)
        except Exception:
            pass
    return {"used_carousel": [], "used_image": [], "used_artikel": [],
            "used_slots": [], "log": []}

def save_state(st):
    with open(STATE_FILE, "w") as f:
        json.dump(st, f, indent=2, ensure_ascii=False)

def h(text):
    return hashlib.md5(text.lower().strip().encode("utf-8")).hexdigest()

def now_wib():
    return datetime.now(WIB)

def next_slots(n, st):
    """n slot WIB berikutnya (jam belum lewat, belum tercatat)."""
    out = []
    cur = now_wib()
    day = cur.date()
    while len(out) < n:
        for hour in SLOT_HOURS:
            dt = datetime(day.year, day.month, day.day, hour, tzinfo=WIB)
            key = dt.strftime("%Y-%m-%dT%H:%M")
            if dt > cur and key not in st["used_slots"]:
                out.append(dt)
                if len(out) >= n:
                    return out
        day += timedelta(days=1)
    return out

def log_line(st, msg):
    st["log"].append(msg)
    with open(LOG_FILE, "a") as f:
        f.write(f"{datetime.now(WIB).isoformat()} | {msg}\n")

# ────────────────────── POOL CAROUSEL ──────────────────────

def _carousel(key, name, folder, files, caption, brand):
    return {"key": key, "name": name, "folder": folder, "files": files,
            "caption": caption, "brand": brand}

CAROUSELS = []
BASE = WORK

# 1. DMark v2 final (6 slide)
CAROUSELS.append(_carousel(
    "dmark_v2_final", "DMark — Kenalan", os.path.join(BASE, "dmark_carousel_v2_final"),
    ["01_cover.png", "02_kenalan.png", "03_mengapa.png", "04_layanan.png",
     "05_kenapa_dmark.png", "06_cta.png"],
    "Desain bukan cuma soal cantik — brand kuat dibangun dari strategi visual yang konsisten.\n\nSwipe untuk kenalan sama DMark Creative, partner visual yang bikin brand Anda diingat. 🎨\n\nwww.dmark.id",
    "dmark"))

# 2. StudioSoft GEO (5 slide)
CAROUSELS.append(_carousel(
    "ss_geo", "StudioSoft — SEO vs GEO", os.path.join(BASE, "studiosoft_geo_carousel"),
    ["01_cover.jpg", "02_slide.jpg", "03_slide.jpg", "04_slide.jpg", "05_slide.jpg"],
    "Google sekarang kasih jawaban AI langsung di hasil pencarian. Website Anda siap?\n\nSEO vs GEO, dan langkah mulai — dirangkum 5 slide. 🚀\n\nstudiosoft.id",
    "studiosoft"))

# 3. StudioSoft HC01 (5 slide)
CAROUSELS.append(_carousel(
    "ss_hc01", "StudioSoft — Healthcare Digital 1", os.path.join(BASE, "studiosoft_hc01_carousel"),
    ["01_cover.png", "02_perilaku.png", "03_pilar.png", "04_kenapa.png", "05_cta.png"],
    "Pasien sekarang cari RS & klinik lewat Google dulu, baru datang.\n\nStrategi digital untuk layanan kesehatan ada di slide ini. 🏥\n\nstudiosoft.id",
    "studiosoft"))

# 4. StudioSoft HC02 (5 slide)
CAROUSELS.append(_carousel(
    "ss_hc02", "StudioSoft — Healthcare Digital 2", os.path.join(BASE, "studiosoft_hc02_carousel"),
    ["01_cover.png", "02_mengapa.png", "03_langkah.png", "04_insight.png", "05_cta.png"],
    "RS & klinik yang menang di 2026 adalah yang paham data pasien.\n\n5 slide singkat: kenapa harus, langkahnya, dan insight-nya. 💡\n\nstudiosoft.id",
    "studiosoft"))

# 5. StudioSoft artikel1_agustus (5 slide)
CAROUSELS.append(_carousel(
    "ss_artikel1", "StudioSoft — Artikel Agustus", os.path.join(BASE, "studiosoft_carousel", "artikel1_agustus"),
    ["01_cover.png", "02_fakta.png", "03_alasan.png", "04_risiko.png", "05_cta.png"],
    "Satu halaman di website yang paling sering dilupakan bisnis — padahal menentukan konversi.\n\nSwipe untuk tahu halaman apa itu. 📄\n\nstudiosoft.id",
    "studiosoft"))

# 6. StudioSoft SEO series (5 slide)
CAROUSELS.append(_carousel(
    "ss_seo", "StudioSoft — Apa itu SEO", os.path.join(BASE, "studiosoft_carousel"),
    ["01_cover.png", "02_apa_itu_seo.png", "03_mengapa_penting.png", "04_jenis_seo.png", "05_cta.png"],
    "SEO bukan sulap — ada langkah yang bisa diikuti siapa pun.\n\nDari nol sampai website naik peringkat, dirangkum 5 slide. 📈\n\nstudiosoft.id",
    "studiosoft"))

# 7. DMark tips listicle (5 slide)
CAROUSELS.append(_carousel(
    "dmark_listicle", "DMark — 5 Strategi Digital Marketing", os.path.join(BASE, "dmark_tips_listicle"),
    ["listicle_01_cover.png", "listicle_02_seo.png", "listicle_03_sem.png",
     "listicle_04_smo.png", "listicle_05_kombinasi.png"],
    "5 strategi digital marketing dalam 5 slide — simpan buat referensi.\n\nSEO, SEM, SMO, sampai kombinasinya. 📌\n\nwww.dmark.id",
    "dmark"))

# 8. DMark tools (6 slide)
CAROUSELS.append(_carousel(
    "dmark_tools", "DMark — Tools Gratis", os.path.join(BASE, "dmark_tips_listicle"),
    ["tools_01_cover.png", "tools_02_canva.png", "tools_03_ahrefs.png",
     "tools_04_buffer.png", "tools_05_gsc.png", "tools_06_tips.png"],
    "Tools gratis yang bikin kerja 2x lebih efisien — lengkap di slide.\n\nCanva, Ahrefs, Buffer, Google Search Console. 🛠️\n\nwww.dmark.id",
    "dmark"))

# 9. DMark personal branding (5 slide)
CAROUSELS.append(_carousel(
    "dmark_pb", "DMark — Personal Branding", os.path.join(BASE, "dmark_tips_listicle"),
    ["pb_01_cover.png", "pb_02_apa.png", "pb_03_kenapa.png", "pb_04_cara.png", "pb_05_mulai.png"],
    "Personal branding: apa, kenapa penting, dan cara memulainya.\n\nRangkuman 5 slide buat siapa pun yang mau dikenal sebagai ahli. 🌟\n\nwww.dmark.id",
    "dmark"))

# 10. StudioSoft August carousels (dari schedule_log.json — 30 set × 5 slide)
try:
    with open(os.path.join(BASE, "studiosoft_august_carousels", "schedule_log.json")) as f:
        AUG = json.load(f)
    for a in AUG:
        day = a["day"]
        theme = a["theme"]
        folder = os.path.join(BASE, "studiosoft_august_carousels", f"2026-08-{day:02d}_{theme}")
        if not os.path.isdir(folder):
            continue
        files = sorted(x for x in os.listdir(folder) if x.startswith("slide_"))
        if len(files) < 2:
            continue
        title = a.get("title", f"Edukasi Hari ke-{day}")
        CAROUSELS.append(_carousel(
            f"ss_aug_{day:02d}", f"StudioSoft — {title}",
            folder, files,
            f"{title} — slide edukasi singkat biar bisnis Anda makin paham digital. 📚\n\nSwipe sampai habis ya!\n\nstudiosoft.id",
            "studiosoft"))
except Exception as e:
    print(f"⚠️ August carousels tidak dimuat: {e}")

# ─────────────────────── POOL IMAGE ───────────────────────

IMAGES = []
IMG_CAPTIONS = {
    "01_blue_digital_marketing.png": "Digital marketing itu sistem, bukan cuma pasang iklan. Pahami alurnya, hasilnya mengikuti. 📊",
    "02_blue_website_optimasi.png": "Website itu aset — rawat seperti toko fisik: cepat, rapi, dan mudah ditemukan. 🏪",
    "03_blue_3_hal_bisnis.png": "3 hal yang paling menentukan bisnis online: website, konten, dan konsistensi. Cek punya Anda sudah lengkap? ✅",
    "04_white_leadership.png": "Pemimpin bukan yang paling pintar, tapi yang paling konsisten mengambil keputusan. 💼",
    "05_white_brand.png": "Brand itu janji. Setiap konten yang Anda terbitkan adalah buktinya. 🤝",
    "06_white_gagal.png": "Gagal itu data, bukan vonis. Pelajari, sesuaikan, lanjutkan. 📉→📈",
    "07_orange_agency_lain.png": "Beda agency, beda pendekatan. Pilih yang paham tujuan bisnis Anda, bukan cuma jualan jasa. 🔍",
    "08_orange_bisnis_marathon.png": "Bisnis itu maraton. Ritme harian yang stabil menang atas sprint seminggu. 🏃",
    "09_blue_ai.png": "AI mengubah cara orang mencari dan memutuskan. Brand yang siap akan menang. 🤖",
    "10_blue_geo.png": "GEO: optimasi supaya brand disebut di jawaban AI. Era baru search sudah mulai. 🌐",
    "11_white_meaning.png": "Jangan cari jati diri — ciptakan lewat hal-hal berarti yang Anda lakukan hari ini. ✨",
    "12_white_filosofi.png": "Filosofi sederhana: lakukan yang benar, ulangi, dan biarkan waktu bekerja. ⏳",
    "13_orange_website.png": "Website adalah kantor utama bisnis di internet — buka 24 jam tanpa libur. 🏢",
    "14_orange_kesalahan.png": "Kesalahan paling umum UMKM: bikin website lalu dibiarkan saja. Perbarui terus! 🔄",
    "15_blue_jangan_post.png": "Jangan posting kalau tidak menambah nilai buat audiens. Kualitas > kuantitas. 🎯",
    "16_blue_personal_branding.png": "Personal branding: tentukan keahlian, konsisten berbagi, dan bangun otoritas. 🌟",
    "17_white_mindset.png": "Mindset berkembang: 'belum bisa' bukan 'tidak bisa'. Belajar itu proses seumur hidup. 🧠",
    "18_white_modal.png": "Modal terbesar bisnis bukan uang — tapi keberanian mulai dan konsistensi. 💪",
    "19_orange_klien_ideal.png": "Kenali klien ideal Anda. Melayani semua orang = melayani tidak ada orang dengan baik. 🎯",
    "20_orange_klien_pergi.png": "Klien pergi bukan karena harga — tapi karena merasa tidak dihargai. Jaga relasi! ❤️",
}
for fn in sorted(os.listdir(os.path.join(BASE, "uky_threads_images"))):
    if fn.lower().endswith((".png", ".jpg", ".jpeg", ".webp")):
        cap = IMG_CAPTIONS.get(fn, f"Catatan singkat buat pelaku bisnis digital hari ini. 💡\n\nstudiosoft.id")
        IMAGES.append({"file": os.path.join(BASE, "uky_threads_images", fn),
                       "caption": cap})

# ────────────────────── POOL ARTIKEL ──────────────────────

def fetch_rss_artikel():
    """Ambil artikel terbaru dari 4 web via RSS. Return list (web, title, link)."""
    out = []
    for web, site in WEBS.items():
        for feed in (f"{site}/feed", f"{site}/feed/"):
            try:
                req = urllib.request.Request(feed, headers={"User-Agent": "Mozilla/5.0"})
                with urllib.request.urlopen(req, timeout=12) as r:
                    raw = r.read(400_000)
                root = ET.fromstring(raw)
                for item in root.iter("item"):
                    title = (item.findtext("title") or "").strip()
                    link = (item.findtext("link") or "").strip()
                    if title and link:
                        out.append({"web": web, "title": title, "link": link})
                break
            except Exception:
                continue
    # dedup
    seen, uniq = set(), []
    for a in out:
        if a["link"] not in seen:
            seen.add(a["link"])
            uniq.append(a)
    return uniq

def check_link(url):
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}, method="HEAD")
        with urllib.request.urlopen(req, timeout=12) as r:
            return r.status == 200
    except Exception:
        try:
            req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
            with urllib.request.urlopen(req, timeout=12) as r:
                return r.status == 200
        except Exception:
            return False

def artikel_caption(art):
    t = art["title"]
    if len(t) > 110:
        t = t[:107].rsplit(" ", 1)[0] + "…"
    openings = [
        "Baru saya baca artikel menarik dari {brand}, dan menurut saya worth dibagikan:",
        "Satu bacaan yang saya rekomendasikan buat pebisnis digital:",
        "Dari blog {brand}, ada insight yang sayang dilewatkan:",
        "Buat yang lagi belajar digital marketing, ini bacaan bagus:",
    ]
    brand = {
        "studiosoft": "StudioSoft",
        "dmark": "DMark",
        "abimata": "Abimata Consultant",
    }[art["web"]]
    opening = random.choice(openings).format(brand=brand)
    return f"{opening}\n\n{t}\n\n{art['link']}"

# ─────────────────────── SCHEDULER ───────────────────────

def upload_slides(c, car, key):
    """Upload semua slide → URL JPG di WP. Return list medias."""
    medias = []
    for i, fn in enumerate(car["files"]):
        path = os.path.join(car["folder"], fn)
        if not os.path.exists(path):
            continue
        name = f"uky_{key}_{i+1}_{os.path.splitext(fn)[0]}"
        url = c.upload_to_wp(path, filename=name + os.path.splitext(fn)[1])
        if not url:
            raise RuntimeError(f"Upload gagal: {path}")
        medias.append({"url": url, "type": "image", "alt": name})
    if len(medias) < 2:
        raise RuntimeError(f"Carousel {key} slide tidak cukup")
    return medias

def run(slots_n=3):
    st = load_state()
    c = ReplizClient()
    acc = ACC.UKY_THREADS
    slots = next_slots(slots_n, st)

    # bangun antrian konten: carousel → artikel → image (variasi)
    pool_c = [x for x in CAROUSELS if x["key"] not in st["used_carousel"]]
    pool_i = [x for x in IMAGES if h(x["file"]) not in st["used_image"]]
    try:
        arts = fetch_rss_artikel()
    except Exception as e:
        arts = []
        print("⚠️ RSS gagal:", e)
    pool_a = [x for x in arts if x["link"] not in st["used_artikel"]]
    # verifikasi link artikel
    pool_a_ok = []
    for a in pool_a[:25]:
        if check_link(a["link"]):
            pool_a_ok.append(a)
    pool_a = pool_a_ok

    print(f"📦 Pool: {len(pool_c)} carousel | {len(pool_i)} image | {len(pool_a)} artikel")
    if not slots:
        print("⚠️ Tidak ada slot tersedia.")
        return

    order = []
    kinds = ["carousel", "artikel", "image"]
    ki = 0
    for _ in range(len(slots)):
        if not any([pool_c, pool_i, pool_a]):
            break
        for _k in range(3):
            kind = kinds[ki % 3]
            ki += 1
            if kind == "carousel" and pool_c:
                order.append(("carousel", pool_c.pop(0))); break
            elif kind == "artikel" and pool_a:
                order.append(("artikel", pool_a.pop(0))); break
            elif kind == "image" and pool_i:
                order.append(("image", pool_i.pop(0))); break
        else:
            # ambil apa pun yang tersisa
            if pool_c: order.append(("carousel", pool_c.pop(0)))
            elif pool_a: order.append(("artikel", pool_a.pop(0)))
            elif pool_i: order.append(("image", pool_i.pop(0)))

    print(f"\n📅 Jadwal {len(order)} konten ke Threads @ukyhandoko:\n")
    for (kind, item), slot in zip(order, slots):
        iso = slot.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")  # UTC benar
        try:
            if kind == "carousel":
                medias = upload_slides(c, item, item["key"])
                res = c.schedule_album(acc, item["caption"], iso, medias, title=item["name"])
                st["used_carousel"].append(item["key"])
                tag = f"ALBUM {item['name']} ({len(medias)} slide)"
            elif kind == "image":
                url = c.upload_to_wp(item["file"], filename="uky_" + os.path.basename(item["file"]))
                res = c.create_schedule(acc, item["caption"], iso, title="",
                                        type_="image",
                                        medias=[{"url": url, "type": "image", "alt": "uky"}])
                st["used_image"].append(h(item["file"]))
                tag = f"IMAGE {os.path.basename(item['file'])}"
            else:  # artikel
                cap = artikel_caption(item)
                if len(cap) > 500:
                    cap = cap[:497].rsplit(" ", 1)[0] + "…"
                res = c.schedule_text(acc, cap, iso)
                st["used_artikel"].append(item["link"])
                tag = f"ARTIKEL {item['web']}: {item['title'][:50]}"
            st["used_slots"].append(slot.strftime("%Y-%m-%dT%H:%M"))
            log_line(st, f"✅ {slot.strftime('%d-%m %H:%M WIB')} | {tag}")
            print(f"  ✅ {slot.strftime('%d-%m %H:%M WIB')} → {tag}")
        except Exception as e:
            print(f"  ❌ {slot.strftime('%d-%m %H:%M WIB')} → {kind} gagal: {str(e)[:100]}")
            log_line(st, f"❌ {slot.strftime('%d-%m %H:%M WIB')} | {kind} gagal: {e}")
        # simpan state tiap iterasi (biar tidak dobel kalau error di tengah)
        save_state(st)

    save_state(st)
    print("\n📌 Selesai. Log: uky_attractive_log.txt")

def show():
    st = load_state()
    print("State:")
    print("  Carousel terpakai:", len(st["used_carousel"]))
    print("  Image terpakai:", len(st["used_image"]))
    print("  Artikel terpakai:", len(st["used_artikel"]))
    print("  Slot terpakai:", len(st["used_slots"]))
    print("\nSisa pool carousel:")
    for x in CAROUSELS:
        if x["key"] not in st["used_carousel"]:
            print(f"  - {x['key']} | {x['name']}")

if __name__ == "__main__":
    args = sys.argv[1:]
    if "--list" in args:
        show()
    else:
        n = 3
        if "--slots" in args:
            n = int(args[args.index("--slots") + 1])
        run(slots_n=n)
