#!/usr/bin/env python3
"""Search/download Pixabay music via the Pixabay audio API and mix under a video.

This is intentionally strict:
- reads API key from ~/.hermes/secrets/pixabay_api_key
- parses either raw key or PIXABAY_API=<key>
- refuses to scrape Cloudflare-protected pages
- only uses official Pixabay audio API when available
- mixes with ffmpeg sidechain ducking under voice
"""
from __future__ import annotations
import argparse, json, os, re, subprocess, sys
from pathlib import Path
from urllib.parse import urlparse

import requests

KEY_PATH = Path.home() / ".hermes" / "secrets" / "pixabay_api_key"
AUDIO_API = "https://pixabay.com/api/audio/"


def load_key() -> str:
    raw = KEY_PATH.read_text(encoding="utf-8").strip()
    return raw.split("=", 1)[1].strip() if "=" in raw else raw


def api_get(params: dict) -> dict:
    key = load_key()
    r = requests.get(AUDIO_API, params={"key": key, **params}, headers={"User-Agent": "Mozilla/5.0"}, timeout=30)
    if r.status_code == 403:
        raise SystemExit("Pixabay audio API returned 403 Access denied. Image API key works, but this key/account currently lacks audio API access.")
    if not r.ok:
        raise SystemExit(f"Pixabay audio API failed: HTTP {r.status_code}: {r.text[:300]}")
    return r.json()


def pick_audio_url(hit: dict) -> str | None:
    for key in ("audio", "audio_url", "previewURL", "downloadURL", "url"):
        val = hit.get(key)
        if isinstance(val, str) and val.startswith("http"):
            return val
    # Some responses may nest audio variants.
    for val in hit.values():
        if isinstance(val, dict):
            for sub in val.values():
                if isinstance(sub, str) and sub.startswith("http") and ".mp3" in sub:
                    return sub
    return None


def search(query: str, per_page: int = 10) -> list[dict]:
    data = api_get({"q": query, "per_page": per_page, "safesearch": "true"})
    return data.get("hits", [])


def download(url: str, out: Path) -> None:
    out.parent.mkdir(parents=True, exist_ok=True)
    with requests.get(url, headers={"User-Agent": "Mozilla/5.0", "Referer": "https://pixabay.com/"}, timeout=60, stream=True) as r:
        if not r.ok:
            raise SystemExit(f"Audio download failed HTTP {r.status_code}: {r.text[:300]}")
        with out.open("wb") as f:
            for chunk in r.iter_content(1024 * 256):
                if chunk:
                    f.write(chunk)


def ffprobe_duration(path: Path) -> float:
    return float(subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", str(path)], text=True).strip())


def mix(video: Path, music: Path, output: Path, volume: float) -> None:
    output.parent.mkdir(parents=True, exist_ok=True)
    cmd = [
        "ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
        "-i", str(video), "-stream_loop", "-1", "-i", str(music),
        "-filter_complex",
        f"[1:a]volume={volume}[m0];[m0][0:a]sidechaincompress=threshold=0.015:ratio=8:attack=25:release=350:makeup=1[mduck];[0:a][mduck]amix=inputs=2:duration=first:dropout_transition=0,alimiter=limit=0.95[a]",
        "-map", "0:v:0", "-map", "[a]", "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", str(output),
    ]
    subprocess.check_call(cmd)


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--query", default="dark suspense background")
    p.add_argument("--video", type=Path, required=False)
    p.add_argument("--output", type=Path, required=False)
    p.add_argument("--volume", type=float, default=0.18)
    p.add_argument("--pick", type=int, default=0, help="0-based hit index")
    args = p.parse_args()
    hits = search(args.query)
    summary = []
    for h in hits:
        summary.append({k: h.get(k) for k in ("id", "title", "duration", "genre", "tags", "user", "pageURL")})
    print(json.dumps({"query": args.query, "hits": summary}, indent=2, ensure_ascii=False))
    if not args.video:
        return 0
    if not hits:
        raise SystemExit("No Pixabay music hits.")
    hit = hits[args.pick]
    audio_url = pick_audio_url(hit)
    if not audio_url:
        raise SystemExit("Selected Pixabay hit has no downloadable audio URL in API response.")
    safe = re.sub(r"[^a-zA-Z0-9_.-]+", "_", str(hit.get("title") or hit.get("id") or "pixabay_track"))[:80]
    track = Path("data/audio_beds/pixabay") / f"{safe}.mp3"
    download(audio_url, track)
    output = args.output or args.video.with_name(args.video.stem + "_pixabay_music.mp4")
    mix(args.video, track, output, args.volume)
    print(json.dumps({"selected": summary[args.pick], "track_path": str(track), "mixed_output": str(output), "video_duration": ffprobe_duration(output)}, indent=2, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
