"""Minimal Sonilo video-to-sound-effects workflow.

Run:
    export SONILO_API_KEY=sk_...
    python video-to-sfx-minimal.py

Optional:
    export SONILO_VIDEO_URL=https://your-public-video.mp4

Default video input uses Sonilo's public demo clip:
https://cdn.sonilo.com/media/new-demos/ShortDrama_1.mp4

This script calls POST /v1/video-to-sfx, polls GET /v1/tasks/{task_id}, and
saves the returned sound-effects audio as output_sfx.m4a. It never creates
placeholder output files.
"""

import os
import sys
import time
from pathlib import Path

import requests

API_BASE = "https://api.sonilo.com/v1"
DEMO_VIDEO_URL = "https://cdn.sonilo.com/media/new-demos/ShortDrama_1.mp4"
OUTPUT_FILE = Path("output_sfx.m4a")
USER_AGENT = "SoniloPythonExample/1.0 (+https://platform.sonilo.com/docs)"


def retry_after_seconds(resp, attempt):
    value = resp.headers.get("Retry-After")
    if value:
        try:
            return max(1, int(value))
        except ValueError:
            return 5
    return min(2**attempt, 30)


def classify_error(resp):
    body = resp.text
    code = f"http_{resp.status_code}"
    message = body
    try:
        payload = resp.json()
        code = payload.get("code", code)
        message = payload.get("message", body)
    except ValueError:
        pass
    if resp.status_code == 402 and code == "payment_required":
        return "insufficient_balance", (
            "insufficient_balance: add balance or use an eval key with enough "
            f"credits. API returned payment_required: {message}"
        )
    return code, f"Sonilo request failed with HTTP {resp.status_code}: {message}"


def request_with_backoff(method, url, **kwargs):
    for attempt in range(1, 6):
        resp = requests.request(method, url, timeout=kwargs.pop("timeout", 60), **kwargs)
        if resp.status_code != 429:
            return resp
        delay = retry_after_seconds(resp, attempt)
        resp.close()
        print(f"Rate limited. Retrying after {delay}s...", file=sys.stderr)
        time.sleep(delay)
    raise RuntimeError("rate_limit_exceeded: still receiving 429 after retries")


def require_ok(resp):
    if resp.ok:
        return
    _, message = classify_error(resp)
    raise RuntimeError(message)


def main():
    api_key = os.environ.get("SONILO_API_KEY")
    if not api_key:
        raise RuntimeError("Set SONILO_API_KEY before running this script")

    video_url = os.environ.get("SONILO_VIDEO_URL", DEMO_VIDEO_URL)
    headers = {
        "Authorization": f"Bearer {api_key}",
        "User-Agent": USER_AGENT,
    }

    submit = request_with_backoff(
        "POST",
        f"{API_BASE}/video-to-sfx",
        headers=headers,
        files={
            "video_url": (None, video_url),
            "prompt": (None, "realistic frame-synced sound effects, no music"),
        },
        timeout=120,
    )
    require_ok(submit)
    payload = submit.json()
    task_id = payload["task_id"]
    print(f"Submitted task {task_id}")

    for _ in range(120):
        task = request_with_backoff(
            "GET",
            f"{API_BASE}/tasks/{task_id}",
            headers=headers,
            timeout=30,
        )
        require_ok(task)
        result = task.json()
        status = result.get("status")
        if status == "succeeded":
            audio = result.get("audio") or {}
            audio_url = audio.get("url")
            if not audio_url:
                raise RuntimeError("missing_audio_url: succeeded task had no audio.url")
            audio_resp = request_with_backoff(
                "GET",
                audio_url,
                headers={"User-Agent": USER_AGENT},
                timeout=120,
            )
            require_ok(audio_resp)
            if not audio_resp.content:
                raise RuntimeError("empty_audio: downloaded audio was empty")
            OUTPUT_FILE.write_bytes(audio_resp.content)
            print(f"Saved {OUTPUT_FILE} ({OUTPUT_FILE.stat().st_size} bytes)")
            return
        if status == "failed":
            error = result.get("error") or {}
            code = error.get("code", "task_failed")
            message = error.get("message", "Sonilo video-to-sfx task failed")
            raise RuntimeError(f"{code}: {message}")
        time.sleep(3)

    raise RuntimeError("timeout: task did not finish within 6 minutes")


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(1)
