#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""IG StudioSoft — story 3x/hari (07:00, 14:00, 20:00 WIB) dari video rakitan slide Threads SS.

Perintah Bos 29 Sep 2026: "ada banyak carousel yang ada di Thread StudioSoft ... buat video dan
tayangkan di IG StudioSoft, pakai slide yang ada / yang sudah digenerate untuk Threads aja, tidak
usah generate baru ... pastikan tiap hari tayang story nya 3 kali di IG StudioSoft jam 07.00, 14.00,
20.00 WIB".

Cara kerja:
  - Sumber = slide carousel `ss_hc*` di `openclaw-img/` (seri yang dipakai di Threads StudioSoft).
    TIDAK ada desain baru — hanya merakit slide yang sudah ada jadi video 9:16.
  - Video: 5 slide x 3,5 dtk = 17,5 dtk, 1080x1920 h264+aac (batas story IG 3-60 dtk).
  - Jadwal: `type=story`, caption KOSONG (story IG memang tanpa caption), `replies=[]`.
  - Rotasi seri deterministik dari tanggal -> idempoten, tidak butuh state file.
  - Horizon bergulir: pastikan 3 slot hari ini + 3 hari ke depan selalu terisi.

Pakai:
  python3 ss_ig_story_3x.py                 # status + rencana (dry-run)
  python3 ss_ig_story_3x.py --videos        # rakit video yang belum ada
  python3 ss_ig_story_3x.py --go            # rakit video + buat jadwal + verifikasi
  python3 ss_ig_story_3x.py --go --hari 7   # horizon 7 hari
"""
import argparse
import datetime
import importlib.util
import os
import re
import sys
import time

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
IMG = f"{WS}/openclaw-img"
sys.path.insert(0, WS)
from repliz_client import get_client  # noqa: E402

REEL_LIB = ("/home/mantapman/.hermes/profiles/atun/skills/social-media/"
            "atun-social-automation/scripts/build_reel_from_slides.py")
# Lib itu mengharapkan nama file `<prefix>_NN.jpg`; slide SS bernama `<seri>_NN_<label>.jpg`,
# jadi filtergraph-nya dipakai ulang lewat `build_files()` di bawah (bukan `brs.build`).


IG_SS = "6a38ac33c5ff4ce3a3231232"
BASE = "https://mantapserver.tail3947bb.ts.net/openclaw-img/"
WIB = datetime.timezone(datetime.timedelta(hours=7))
SLOTS = [7, 14, 20]           # jam WIB
TOPIC = "IG StudioSoft Story Harian"

# Seri carousel Threads StudioSoft (`ss_hc*` di openclaw-img). Urut dari yang paling baru.
SERI = [
    "ss_hc50_seolokal", "ss_hc49_seoklinikcan", "ss_hc48_channel10", "ss_hc47_pasienbaru",
    "ss_hc46_klinikcan", "ss_hc45_kontenhc", "ss_hc44_gbp", "ss_hc43_dmrs26",
    "ss_hc42_seoklinik", "ss_hc41_analytics", "ss_hc40_klinikkecil", "ss_hc39_iklanfb",
    "ss_hc38_website", "ss_hc37_edukasi", "ss_hc36_klinikdm", "ss_hc32", "ss_hc31",
    "ss_hc30", "ss_hc29", "ss_hc28", "ss_hc27", "ss_hc26", "ss_hc25_video",
    "ss_hc24_testimoni", "ss_hc23_googleads", "ss_hc22_digitalmkt", "ss_hc21_websiters",
    "ss_hc20_edukasi", "ss_hc19_landing", "ss_hc18_kpi", "ss_hc17_wachatbot",
    "ss_hc16_booking",
]


def slides(seri):
    """Nomor slide yang file-nya ada di openclaw-img (urut)."""
    nums = set()
    for f in os.listdir(IMG):
        m = re.match(re.escape(seri) + r"_(\d\d)_.+\.jpg$", f)
        if m:
            nums.add(int(m.group(1)))
    return sorted(nums)


def video_path(seri):
    """ss_hc50_seolokal -> ss_storyvid_hc50_seolokal.mp4"""
    return f"{IMG}/ss_storyvid_{seri[3:]}.mp4"


def video_url(seri):
    return BASE + os.path.basename(video_path(seri))


def build_files(files, out, dur=3.5, fade=0.4):
    """Rakit video 1080x1920 dari daftar file slide (latar blur + slide utuh di tengah).

    Filtergraph sama dengan `build_reel_from_slides.py` di skill; label unik per input
    (`a0/b0/bg0/fg0`) karena label berulang pernah bikin ffmpeg exit 254 tanpa pesan.
    """
    import subprocess
    W, H = 1080, 1920
    n = len(files)
    cmd = ["ffmpeg", "-y", "-v", "error"]
    for f in files:
        cmd += ["-loop", "1", "-t", str(dur), "-i", f]
    parts, labels = [], []
    for i in range(n):
        parts.append(
            f"[{i}:v]split=2[a{i}][b{i}];"
            f"[a{i}]scale={W}:{H}:force_original_aspect_ratio=increase,"
            f"crop={W}:{H},boxblur=28:2[bg{i}];"
            f"[b{i}]scale={W}:-2[fg{i}];"
            f"[bg{i}][fg{i}]overlay=(W-w)/2:(H-h)/2,"
            f"fade=t=in:st=0:d={fade},fade=t=out:st={dur - fade}:d={fade},"
            f"setsar=1[v{i}]"
        )
        labels.append(f"[v{i}]")
    fc = ";".join(parts) + ";" + "".join(labels) + f"concat=n={n}:v=1:a=0[vout]"
    cmd += [
        "-f", "lavfi", "-t", str(dur * n), "-i", "anullsrc=r=44100:cl=stereo",
        "-filter_complex", fc, "-map", "[vout]", "-map", f"{n}:a",
        "-c:v", "libx264", "-preset", "medium", "-crf", "22",
        "-pix_fmt", "yuv420p", "-profile:v", "high", "-level", "4.0",
        "-r", "30", "-c:a", "aac", "-b:a", "128k", "-shortest",
        "-movflags", "+faststart", out,
    ]
    print(f"  bikin {os.path.basename(out)} ({n} slide, {dur * n:.1f} dtk)")
    subprocess.run(cmd, check=True)


def files_seri(seri):
    """Daftar path slide seri itu, urut nomor slide."""
    out = []
    for f in sorted(os.listdir(IMG)):
        if re.match(re.escape(seri) + r"_\d\d_.+\.jpg$", f):
            out.append(f"{IMG}/{f}")
    return out


def rakit(seri, force=False):
    """Rakit video 9:16 dari slide seri itu."""
    out = video_path(seri)
    if os.path.exists(out) and os.path.getsize(out) > 100_000 and not force:
        return out
    files = files_seri(seri)
    if not files:
        print(f"  ⚠️ {seri}: tidak ada file slide — SKIP")
        return None
    build_files(files, out)
    return out


def seri_untuk(tgl, slot_idx):
    """Rotasi deterministik: tanggal + jam -> seri."""
    n = (tgl.toordinal() * len(SLOTS) + slot_idx) % len(SERI)
    return SERI[n]


def semua_jadwal(c):
    docs, page = [], 1
    while page <= 200:
        r = None
        for a in range(6):
            try:
                r = c.get_schedules(page=page, limit=100)
                break
            except Exception as e:
                print(f"    ⚠️ halaman {page} gagal ({str(e)[:60]}), ulang…")
                time.sleep(5 + 3 * a)
        if r is None:
            raise SystemExit("gagal membaca jadwal Repliz")
        b = r.get("docs") or []
        if not b:
            break
        docs += b
        if len(b) < 100:
            break
        page += 1
    return docs


def kunci_story(docs):
    """set (scheduleAt) jadwal story IG SS yang sudah ada — anti dobel."""
    ada = set()
    for d in docs:
        if d.get("accountId") == IG_SS and d.get("type") == "story":
            ada.add(d.get("scheduleAt"))
    return ada


def iso_utc(tgl, jam):
    dt = datetime.datetime(tgl.year, tgl.month, tgl.day, jam, tzinfo=WIB)
    return dt.astimezone(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--go", action="store_true")
    ap.add_argument("--videos", action="store_true")
    ap.add_argument("--hari", type=int, default=4, help="horizon hari (default 4 = hari ini + 3)")
    ap.add_argument("--force-video", action="store_true")
    a = ap.parse_args()

    hari_ini = datetime.datetime.now(WIB).date()
    sekarang = datetime.datetime.now(datetime.timezone.utc)
    rencana = []
    for ofs in range(a.hari):
        tgl = hari_ini + datetime.timedelta(days=ofs)
        for i, jam in enumerate(SLOTS):
            at = iso_utc(tgl, jam)
            if datetime.datetime.fromisoformat(at.replace("Z", "+00:00")) <= sekarang:
                continue
            rencana.append((at, tgl, jam, seri_untuk(tgl, i)))

    print(f"horizon {a.hari} hari ({hari_ini} s/d {hari_ini + datetime.timedelta(days=a.hari - 1)}) "
          f"— {len(rencana)} slot story, {len(SERI)} seri dalam rotasi")
    for at, tgl, jam, seri in rencana:
        print(f"  {tgl} {jam:02d}:00 WIB | {seri} | {os.path.basename(video_path(seri))} | {at}")

    if a.videos or a.go:
        print("\n=== rakit video ===")
        for seri in sorted({s for _, _, _, s in rencana}):
            out = rakit(seri, force=a.force_video)
            if out:
                print(f"  ✅ {os.path.basename(out)} ({os.path.getsize(out) / 1e6:.2f} MB)")

    if not a.go:
        print("\n(dry-run — pakai --go untuk rakit + jadwalkan)")
        return

    # verifikasi media hidup di kedua ingress sebelum bikin jadwal
    import urllib.request
    urls = sorted({video_url(s) for _, _, _, s in rencana})
    print("\n=== cek media di ingress funnel ===")
    ip = None
    try:
        import subprocess
        ip = subprocess.run(["dig", "+short", "mantapserver.tail3947bb.ts.net", "A", "@1.1.1.1"],
                            capture_output=True, text=True, timeout=20).stdout.split()
    except Exception:
        pass
    for u in urls:
        kode = urllib.request.urlopen(u, timeout=30).getcode()
        print(f"  {kode} {u.split('/')[-1]}")
        if kode != 200:
            raise SystemExit("media tidak 200 — stop, jangan bikin jadwal")

    c = get_client()
    ada = kunci_story(semua_jadwal(c))
    dibuat = []
    print("\n=== buat jadwal ===")
    for at, tgl, jam, seri in rencana:
        if at in ada:
            print(f"  ⏭️  {tgl} {jam:02d}:00 WIB — sudah ada, SKIP")
            continue
        media = [{"url": video_url(seri), "type": "video",
                  "alt": os.path.basename(video_path(seri))}]
        sid = None
        for attempt in range(1, 4):
            try:
                res = c.create_schedule(IG_SS, "", at, title=f"Story IG SS {tgl} {jam:02d}",
                                        topic=TOPIC, type_="story", medias=media, replies=[])
                sid = res.get("scheduleId") or res.get("_id") or res.get("id")
                break
            except Exception as e:
                print(f"    ⚠️ percobaan {attempt}: {str(e)[:100]}")
                time.sleep(8)
        print(f"  {'✅' if sid else '❌'} {tgl} {jam:02d}:00 WIB | {seri} | id={sid}")
        if sid:
            dibuat.append((sid, f"{tgl} {jam:02d}", seri))

    print("\n=== verifikasi (baca ulang per id) ===")
    gagal = 0
    for sid, label, seri in dibuat:
        d = c.get_schedule(sid)
        d = d.get("data") or d
        if isinstance(d, dict) and "docs" in d:
            d = d["docs"][0]
        ok = d.get("status") == "pending"
        gagal += 0 if ok else 1
        print(f"  {'✅' if ok else '⚠️'} {label} | status={d.get('status')} type={d.get('type')} "
              f"at={d.get('scheduleAt')} media={(d.get('medias') or [{}])[0].get('url', '').split('/')[-1]}")
    print(f"\nselesai: {len(dibuat)} jadwal dibuat, {gagal} janggal")


if __name__ == "__main__":
    main()
