#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""tslaw_story_harian.py — STORY harian TS Law (IG + FB), dari konten yang sudah pernah tayang.

Perintah Bos 3 Okt 2026: *"Cek tslaw instagram n facebook. Pasang 1 story tiap hari untuk IG n FB.
Kamu bisa pasang story dari konten yg sudah pernah tayang. Jadikan video slides."*

Yang dikerjakan:
  • Ambil stok desain TS Law yang SUDAH dipakai di jadwal Repliz (IG/FB, status apa pun) dari
    /tmp/tslaw_sched.json (kalau belum ada -> fetch). Urut per seri biar ceritanya nyambung.
  • Rakit 1 video story per hari: 5 slide × 3,5 dtk = 17,5 dtk, kanvas 1080×1920 (latar blur + slide utuh
    di tengah, fade) — filtergraph sama dengan `ss_ig_story_3x.build_files`.
  • Jadwalkan 1 story/hari ke IG TS Law DAN FB TS Law, jam 12:00 WIB, caption KOSONG, replies=[].
    Idempoten per (akun, hari). State: tslaw_story_state.json.

Pakai:
  python3 tslaw_story_harian.py preview 30        # rencana (tanpa efek)
  python3 tslaw_story_harian.py build 30          # rakit video saja (tanpa create)
  python3 tslaw_story_harian.py run 30            # dry-run jadwal
  python3 tslaw_story_harian.py run 30 --go       # rakit + create
  python3 tslaw_story_harian.py verif             # baca balik jadwal story yang ada
"""
import argparse
import datetime
import json
import os
import re
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
IMG = os.path.join(WS, "openclaw-img")
VIDDIR = os.path.join(WS, "tslaw_story")
DUMP = "/tmp/tslaw_sched.json"
STATE = os.path.join(WS, "tslaw_story_state.json")
sys.path.insert(0, WS)
from repliz_client import get_client, ReplizError, IMG_BASE_URL  # noqa: E402

WIB = datetime.timezone(datetime.timedelta(hours=7))
AKUN = {"ig": "6a76a54d62caae1e04f30968", "fb": "6a3b41d1c5ff4ce3a32753e8"}
NAMA_AKUN = {"ig": "TS Law IG", "fb": "TS Law FB"}
HOST = "mantapserver.tail3947bb.ts.net"
INGRESS = ["103.84.155.153", "103.84.155.217"]
JAM = 12                     # 12:00 WIB tiap hari
EPOCH = datetime.date(2026, 10, 4)   # hari ke-1 seri ini -> nama video stabil per TANGGAL
N_VIDEO = 30                 # jumlah video yang dirakit sekali (dipakai bergilir setelahnya)
SLIDE_PER_VIDEO = 5
DUR = 3.5
FADE = 0.4
LANGKAH = 3                  # geser 3 slide/hari supaya antar-hari tidak kembar

URUTAN_SERI = ["tslaw_mitos_fakta", "tslaw_mitos_perjanjian", "tslaw_hukum_perdata",
               "tslaw_pinjol", "tslaw_edu", "tslaw_desain"]


def semua_jadwal():
    if os.path.exists(DUMP) and time.time() - os.path.getmtime(DUMP) < 6 * 3600:
        return json.load(open(DUMP))
    c = get_client()
    docs, page = [], 1
    while page <= 80:
        r = None
        for a in range(5):
            try:
                r = c.get_schedules(page=page, limit=100)
                break
            except Exception:
                time.sleep(4 + 2 * a)
        if r is None:
            raise SystemExit("gagal baca jadwal Repliz")
        b = r.get("docs") or []
        docs += b
        if len(b) < 100:
            break
        page += 1
    json.dump(docs, open(DUMP, "w"))
    return docs


def pool_aset(docs):
    """Gambar TS Law yang sudah pernah dipakai di jadwal IG/FB dan ada file lokalnya."""
    dipakai = set()
    for d in docs:
        if d.get("accountId") not in AKUN.values():
            continue
        for m in (d.get("medias") or []):
            nm = (m.get("url") or "").split("/")[-1]
            if nm.startswith("tslaw_") and re.search(r"\.(jpg|jpeg|png)$", nm, re.I):
                dipakai.add(nm)
    ada = [f for f in os.listdir(IMG) if f in dipakai and os.path.exists(os.path.join(IMG, f))]
    # urutkan per seri lalu nomor
    def kunci(f):
        for i, s in enumerate(URUTAN_SERI):
            if f.startswith(s):
                nom = re.search(r"(\d+)", f[len(s):] or "0")
                return (i, int(nom.group(1)) if nom else 0, f)
        return (len(URUTAN_SERI), 0, f)
    return sorted(ada, key=kunci)


def urutan_hari(pool, hari_ke, n=SLIDE_PER_VIDEO):
    """5 slide hari ke-`hari_ke` (0-based): jendela geser, tanpa duplikat dalam satu video.

    Satu 'lap' = L//LANGKAH hari; lap berikutnya pakai LANGKAH + offset berbeda supaya urutan
    antar-lap tidak kembar (stok 42 file -> lap 1 = 14 hari, lap 2 tidak sama dengan lap 1).
    """
    L = len(pool)
    per_lap = max(1, L // LANGKAH)
    lap, sisa = divmod(hari_ke, per_lap)
    langkah = LANGKAH if lap % 2 == 0 else 5           # 5 relatif prima dgn 42 -> tidak kembar
    i = ((lap * 7) % L + sisa * langkah) % L
    out = []
    while len(out) < n:
        f = pool[i % L]
        i += 1
        if f not in out:
            out.append(f)
    return out


def build_video(files, out):
    W, H = 1080, 1920
    n = len(files)
    cmd = ["ffmpeg", "-y", "-v", "error"]
    for f in files:
        cmd += ["-loop", "1", "-t", str(DUR), "-i", os.path.join(IMG, 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]
    subprocess.run(cmd, check=True, capture_output=True)


def gate(nama):
    for ip in INGRESS:
        code = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "30",
                               "--resolve", f"{HOST}:443:{ip}",
                               f"https://{HOST}/openclaw-img/{nama}"],
                              capture_output=True, text=True).stdout.strip()
        if code != "200":
            return f"{nama} @{ip} → {code}"
    return None


def hari_mulai():
    """Hari pertama yang slot jam 12:00-nya belum lewat (WIB), dihitung dari sekarang."""
    skr = datetime.datetime.now(WIB)
    tgl = skr.date() if skr.hour < JAM else skr.date() + datetime.timedelta(days=1)
    return tgl


def slot_story_terisi(c, aid):
    out = set()
    page = 1
    while page <= 80:
        r = c.get_schedules(page=page, limit=100)
        docs = r.get("docs") or []
        for d in docs:
            if d.get("accountId") == aid and str(d.get("type")) == "story" \
                    and str(d.get("status")) in ("pending", "draft", "processing", "success"):
                out.add(str(d.get("scheduleAt"))[:10])
        if len(docs) < 100:
            break
        page += 1
    return out


def verifikasi(c, sid):
    d = c.get(f"/public/schedule/{sid}")
    d = d.get("data") or d
    return d


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("mode", choices=["preview", "build", "run", "verif"])
    ap.add_argument("hari", nargs="?", type=int, default=30)
    ap.add_argument("--go", action="store_true")
    a = ap.parse_args()

    docs = semua_jadwal()
    pool = pool_aset(docs)
    mulai = hari_mulai()
    print(f"stok desain TS Law yang sudah dipakai di jadwal: {len(pool)} file")
    print(f"mulai: {mulai:%d %b %Y} · jam {JAM:02d}:00 WIB · {a.hari} hari · "
          f"{SLIDE_PER_VIDEO} slide x {DUR}s = {SLIDE_PER_VIDEO * DUR:.1f} dtk/video\n")

    rencana = []
    for i in range(a.hari):
        tgl = mulai + datetime.timedelta(days=i)
        idx = (tgl - EPOCH).days % N_VIDEO          # stabil per tanggal, dipakai bergilir
        files = urutan_hari(pool, idx)
        rencana.append({"hari": i + 1, "tgl": tgl.isoformat(),
                        "video": f"tslaw_story_{idx + 1:02d}.mp4", "slide": files})

    if a.mode == "preview":
        for r in rencana:
            print(f"{r['hari']:>2}. {r['tgl']}  {r['video']}  " + " · ".join(s.replace("tslaw_", "").replace(".jpg", "") for s in r["slide"]))
        return

    if a.mode == "verif":
        c = get_client()
        for k, aid in AKUN.items():
            ada = slot_story_terisi(c, aid)
            print(f"{NAMA_AKUN[k]}: {len(ada)} hari punya story -> {sorted(ada)[:5]} … {sorted(ada)[-3:] if ada else ''}")
        return

    # --- rakit video ---
    os.makedirs(VIDDIR, exist_ok=True)
    perlu = [r for r in rencana if not os.path.exists(os.path.join(VIDDIR, r["video"]))]
    print(f"rakit video: {len(perlu)} dari {len(rencana)} (sisanya sudah ada)")

    def rakit(r):
        tmp = os.path.join(VIDDIR, r["video"])
        build_video(r["slide"], tmp)
        dst = os.path.join(IMG, r["video"])
        if os.path.abspath(tmp) != os.path.abspath(dst):
            subprocess.run(["cp", tmp, dst], check=True)
        return r["video"], os.path.getsize(dst) // 1024

    with ThreadPoolExecutor(max_workers=3) as ex:
        for nm, kb in ex.map(rakit, perlu):
            print(f"   {nm} {kb} KB")
    print("selesai rakit.\n")

    if a.mode == "build":
        return

    # --- jadwalkan ---
    c = get_client()
    state = json.load(open(STATE)) if os.path.exists(STATE) else {"posted": []}
    sudah = {(p["akun"], p["tgl"]) for p in state["posted"]}
    terisi = {k: slot_story_terisi(c, aid) for k, aid in AKUN.items()}
    print(f"slot story yang sudah terisi: IG {len(terisi['ig'])} hari, FB {len(terisi['fb'])} hari")

    for r in rencana:
        for k, aid in AKUN.items():
            if (k, r["tgl"]) in sudah or r["tgl"] in terisi[k]:
                print(f"   ⏭️  {k} {r['tgl']} sudah ada story — SKIP")
                continue
            g = gate(r["video"])
            if g:
                print(f"   ⛔ gate gagal: {g}")
                continue
            at = f"{r['tgl']}T{JAM - 7:02d}:00:00.000Z"          # 12:00 WIB = 05:00Z
            payload = {"accountId": aid, "title": f"Story TS Law {r['video']}",
                       "topic": "Story harian TS Law (video slides konten lama)",
                       "type": "story", "description": "",
                       "medias": [{"url": f"{IMG_BASE_URL}/openclaw-img/{r['video']}",
                                   "type": "video", "alt": r["video"]}],
                       "scheduleAt": at, "replies": []}
            if not a.go:
                print(f"   (dry) {k} {r['tgl']} {JAM:02d}:00 WIB {r['video']} — "
                      f"{' · '.join(s.replace('tslaw_', '').replace('.jpg', '') for s in r['slide'])}")
                continue
            try:
                res = c.post("/public/schedule", payload)
                sid = res.get("scheduleId") or res.get("_id") or res.get("id")
                d = verifikasi(c, sid)
                print(f"   ✅ {k} {r['tgl']} {JAM:02d}:00 WIB | {r['video']} | id={sid} "
                      f"status={d.get('status')} at={d.get('scheduleAt')}")
                state["posted"].append({"akun": k, "tgl": r["tgl"], "video": r["video"],
                                        "slide": r["slide"], "id": sid, "scheduleAt": at})
                json.dump(state, open(STATE, "w"), indent=1, ensure_ascii=False)
            except ReplizError as e:
                print(f"   ❌ {k} {r['tgl']} gagal: {e}")


if __name__ == "__main__":
    try:
        main()
    except ReplizError as e:
        print("ReplizError:", e)
        sys.exit(1)
