#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Story IG StudioSoft sebagai VIDEO bermusik (bukan gambar diam).

Perintah Bos 20 Okt 2026: *"pastikan semua postingan Instagram StudioSoft pakai lagu"*.
IG Graph API tidak punya kolom musik untuk story gambar, jadi satu-satunya jalan: story
dijadikan video pendek (8 dtk) dengan bed musik dibakar ke MP4 — gambar tetap sama
(kanvas 9:16 hasil `ss_ig_story_mirror.kanvas_story`), cuma diberi zoom halus + lagu.

Bed musik: bank sendiri di `musik/` (bebas royalti, buatan `musik_bank.py`), diputar
bergiliran per tanggal supaya tidak sama terus.

Sebagai modul:  video_story(nama_jpg, tgl) → nama berkas mp4
CLI:
  python3 ss_story_musik.py --hari 30          # rencana: story gambar mana yang dijadikan video
  python3 ss_story_musik.py --hari 30 --run    # bangun video + PUT jadwalnya
"""
import argparse
import datetime
import os
import re
import subprocess
import sys
import time

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
sys.path.insert(0, WS)
from repliz_client import get_client  # noqa: E402

IG_SS = "6a38ac33c5ff4ce3a3231232"
IMG_DIR = os.path.join(WS, "openclaw-img")
MUSIK = os.path.join(WS, "musik")
IMG_BASE = "https://mantapserver.tail3947bb.ts.net/openclaw-img/"
INGRESS = ["103.84.155.153", "103.84.155.217"]
WIB = datetime.timezone(datetime.timedelta(hours=7))
DUR = 8.0            # detik; cukup untuk dibaca, tidak bikin story terasa lambat
VOL = 0.75           # bed puncak -2.7 dB → hasil ≈ -4.8 dB
# Story sifatnya edukatif → mayoritas kalem, sesekali ceria ringan.
BED = ["kalem_hangat_24s.wav", "kalem_lembut_24s.wav", "ceria_ringan_24s.wav",
       "kalem_malam_24s.wav", "ceria_cerah_24s.wav"]


def bed_untuk(tgl):
    return os.path.join(MUSIK, BED[tgl.toordinal() % len(BED)])


def nama_video(nama_jpg):
    dasar = re.sub(r"\.(jpe?g|png|webp)$", "", nama_jpg, flags=re.I)
    return f"ss_story_vid_{dasar}.mp4"


def audio_terisi(path):
    p = subprocess.run(["ffmpeg", "-hide_banner", "-i", path, "-af", "volumedetect",
                        "-f", "null", "-"], capture_output=True, text=True)
    m = re.search(r"max_volume: (-?[\d.]+) dB", p.stderr)
    return bool(m) and float(m.group(1)) > -50


def video_story(nama_jpg, tgl, paksa=False):
    """Bangun (kalau belum ada) video story 1080x1920 + musik. Return nama berkas mp4."""
    src = os.path.join(IMG_DIR, nama_jpg)
    if not os.path.exists(src):
        return None
    out_nama = nama_video(nama_jpg)
    out = os.path.join(IMG_DIR, out_nama)
    if os.path.exists(out) and os.path.getsize(out) > 100_000 and not paksa:
        if audio_terisi(out):
            return out_nama
    bed = bed_untuk(tgl)
    if not os.path.exists(bed):
        return None
    fc = (
        f"[0:v]scale=1080:1920:force_original_aspect_ratio=decrease,"
        f"pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=white,"
        f"zoompan=z='min(1.05,1+0.0007*on)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':"
        f"d=1:s=1080x1920:fps=30,format=yuv420p[v];"
        f"[1:a]volume={VOL},afade=t=in:st=0:d=0.6,"
        f"afade=t=out:st={DUR - 1.2:.2f}:d=1.2,atrim=0:{DUR:.2f},asetpts=N/SR/TB[a]"
    )
    tmp = out.replace(".mp4", "_tmp.mp4")
    cmd = ["ffmpeg", "-y", "-v", "error", "-loop", "1", "-framerate", "30", "-t", str(DUR), "-i", src,
           "-stream_loop", "-1", "-i", bed, "-filter_complex", fc,
           "-map", "[v]", "-map", "[a]", "-c:v", "libx264", "-preset", "medium", "-crf", "21",
           "-profile:v", "high", "-level", "4.0", "-pix_fmt", "yuv420p", "-r", "30",
           "-c:a", "aac", "-b:a", "192k", "-ar", "44100", "-shortest",
           "-movflags", "+faststart", tmp]
    subprocess.run(cmd, check=True)
    if not audio_terisi(tmp):
        os.remove(tmp)
        return None
    os.replace(tmp, out)
    print(f"      video story: {out_nama} ({os.path.getsize(out) / 1e6:.2f} MB, "
          f"{os.path.basename(bed)})")
    return out_nama


def semua_jadwal(c):
    docs, page = [], 1
    while page <= 60:
        r = c.get_schedules(page=page, limit=100)
        b = r.get("docs") or []
        if not b:
            break
        docs += b
        if len(b) < 100:
            break
        page += 1
    return docs


def cek_ingress(u):
    ok = True
    for ip in INGRESS:
        p = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "30",
                            "--resolve", f"mantapserver.tail3947bb.ts.net:443:{ip}", u],
                           capture_output=True, text=True)
        ok = ok and p.stdout.strip() == "200"
    return ok


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--hari", type=int, default=30)
    ap.add_argument("--run", action="store_true")
    a = ap.parse_args()

    now = datetime.datetime.now(datetime.timezone.utc)
    batas = now + datetime.timedelta(days=a.hari)
    c = get_client()
    target = []
    for d in semua_jadwal(c):
        if d.get("accountId") != IG_SS:
            continue
        if str(d.get("type")).lower() != "story":
            continue
        if str(d.get("status")).lower() not in ("pending", "process", "processing"):
            continue
        at = datetime.datetime.fromisoformat(str(d.get("scheduleAt")).replace("Z", "+00:00"))
        if not (now <= at <= batas):
            continue
        m = (d.get("medias") or [{}])[0]
        nama = (m.get("url") or "").split("/")[-1]
        if str(m.get("type")).lower() == "video" or nama.endswith(".mp4"):
            continue
        target.append((at, d, nama))

    print(f"story IG SS gambar → video bermusik: {len(target)}")
    gagal = 0
    for at, d, nama in sorted(target, key=lambda x: x[0]):
        label = at.astimezone(WIB).strftime("%d %b %H:%M")
        if not a.run:
            print(f"  rencana {label} {nama[:50]} → {nama_video(nama)[:60]}")
            continue
        vid = video_story(nama, at.date())
        if not vid:
            print(f"  ❌ {label} gagal rakit dari {nama}")
            gagal += 1
            continue
        url = IMG_BASE + vid
        if not cek_ingress(url):
            print(f"  ❌ {label} video tidak 200 di ingress")
            gagal += 1
            continue
        try:
            c.update_schedule(d["_id"], {"medias": [{"url": url, "type": "video", "alt": vid}]})
        except Exception as e:
            print(f"  ❌ {label} PUT gagal: {str(e)[:110]}")
            gagal += 1
            continue
        v = c.get_schedule(d["_id"])
        v = v.get("data") or v
        if isinstance(v, dict) and "docs" in v:
            v = v["docs"][0]
        mm = ((v.get("medias") or [{}])[0])
        ok = str(mm.get("type")).lower() == "video" and (mm.get("url") or "").endswith(".mp4")
        gagal += 0 if ok else 1
        print(f"  {'✅' if ok else '⚠️'} {label} type={v.get('type')} status={v.get('status')} "
              f"media={(mm.get('url') or '').split('/')[-1][:52]}")
        time.sleep(1)
    print(f"selesai: {len(target) - gagal} ok, {gagal} janggal")


if __name__ == "__main__":
    main()
