#!/usr/bin/env python3
"""
Cozypaw Hospital — freesound.org batch audio downloader
Downloads all placeholder audio files (0-byte .ogg) and replaces them with
128 kbps HQ OGG previews from freesound.org.

Prerequisites:
  pip install requests

API key setup (free, ~2 min):
  1. freesound.org → login → click your username → "API credentials"
  2. Click "Apply for an API key"
  3. App name: "Cozypaw Download Script", Description: "Personal game project"
  4. Copy the "Api key" value (the long string) into API_KEY below.

Usage:
  python docs/download_audio.py

Quality note:
  This script downloads the "preview-hq-ogg" (128 kbps OGG Vorbis).
  For a mobile children's game this is indistinguishable from lossless.
  Original-quality downloads require full OAuth2 — not worth the hassle.
"""

import os
import sys
import requests
from pathlib import Path

# ── Fill in your API key here ──────────────────────────────────────────────────
API_KEY = ""   # get your free key at freesound.org → API credentials
# ──────────────────────────────────────────────────────────────────────────────

REPO_ROOT = Path(__file__).parent.parent
BASE_URL   = "https://freesound.org/apiv2"

# Files with confirmed freesound IDs — downloaded by ID, no searching needed.
# CC-BY entries are marked; they need an attribution line in docs/credits-audio.md
KNOWN_IDS: dict[str, tuple[int, str]] = {
    # path                                          id      license
    # ── Sprint 19 — music ──────────────────────────────────────────────────────
    "assets/audio/music/floor_0.ogg":           (725019, "CC0"),
    "assets/audio/music/floor_1.ogg":           (387588, "CC0"),
    "assets/audio/music/floor_2.ogg":           (684511, "CC0"),
    "assets/audio/music/floor_3.ogg":           (723913, "CC0"),
    # ── Sprint 19 — SFX ───────────────────────────────────────────────────────
    "assets/audio/sfx/chest_tap.ogg":           (679772, "CC0"),
    "assets/audio/sfx/item_spawn.ogg":          (683096, "CC0"),
    "assets/audio/sfx/item_drag_start.ogg":     (411177, "CC0"),
    "assets/audio/sfx/item_drop_hand.ogg":      (448086, "CC0"),
    "assets/audio/sfx/item_drop_outfit.ogg":    (161415, "CC-BY 3.0"),  # needs attribution
    "assets/audio/sfx/item_return_chest.ogg":   (740266, "CC0"),
    "assets/audio/sfx/item_drop_floor.ogg":     (449955, "CC0"),
    # ── Sprint 21 — interactive object SFX ────────────────────────────────────
    "assets/audio/sfx/xray_scan.ogg":           (614030, "CC0"),   # Machine beep.wav — INHIVE.NEWERA
    "assets/audio/sfx/tea_pour.ogg":            (116396, "CC0"),   # liquid-pour.mp3 — shakala1
    "assets/audio/sfx/cradle_rock.ogg":         (216877, "CC0"),   # Slow gentle squeaky wooden door — CastIronCarousel
    "assets/audio/sfx/gift_open.ogg":           (676625, "CC0"),   # Rip 8 - Long — NearTheAtmoshphere
    "assets/audio/sfx/ambulance_siren.ogg":     (536773, "CC0"),   # Siren.ogg — egomassive
    "assets/audio/sfx/delivery_cheer.ogg":      (717771, "CC0"),   # victory chime — 1bob
    "assets/audio/sfx/object_tap.ogg":          (817506, "CC0"),   # Soft Interface 01 Tap — tonymadethatt
    # ── Sprint 22 — character & ambient SFX ───────────────────────────────────
    "assets/audio/sfx/ultrasound_heartbeat.ogg":(463202, "CC0"),   # one_beep.wav — Kenneth_Cooney
    "assets/audio/sfx/character_pickup.ogg":    (789840, "CC0"),   # Whoosh Short — FartCTO
    "assets/audio/sfx/character_place.ogg":     (653910, "CC0"),   # soft-hit.wav — Krokulator
    "assets/audio/sfx/character_tap.ogg":       (776443, "CC0"),   # pop out, bubble — chaferwitt
}

# All files now have confirmed IDs in KNOWN_IDS above.
SEARCH_QUERIES: dict[str, tuple[str, float]] = {}

# ── Helpers ────────────────────────────────────────────────────────────────────

def _is_placeholder(path: Path) -> bool:
    """Returns True if the file is missing or 0-byte (i.e. still a placeholder)."""
    return not path.exists() or path.stat().st_size == 0


def _get_sound_info(sound_id: int) -> dict | None:
    url = f"{BASE_URL}/sounds/{sound_id}/"
    r = requests.get(url, params={
        "fields": "id,name,previews,license,username,duration",
        "token":  API_KEY,
    }, timeout=15)
    if r.status_code != 200:
        print(f"  ✗ API error {r.status_code} for ID {sound_id}")
        return None
    return r.json()


def _search_sound(query: str, max_duration: float) -> dict | None:
    r = requests.get(f"{BASE_URL}/search/text/", params={
        "query":     query,
        "filter":    f'license:"Creative Commons 0" duration:[0 TO {max_duration}]',
        "fields":    "id,name,previews,license,username,duration",
        "sort":      "score",
        "page_size": 5,
        "token":     API_KEY,
    }, timeout=15)
    if r.status_code != 200:
        print(f"  ✗ Search API error {r.status_code} for query '{query}'")
        return None
    results = r.json().get("results", [])
    if not results:
        print(f"  ✗ No results for '{query}' under {max_duration}s")
        return None
    return results[0]


def _download_preview(info: dict, dest: Path) -> bool:
    ogg_url = info.get("previews", {}).get("preview-hq-ogg")
    if not ogg_url:
        print(f"  ✗ No HQ OGG preview URL in response")
        return False
    r = requests.get(ogg_url, timeout=30)
    if r.status_code != 200:
        print(f"  ✗ CDN download failed ({r.status_code}): {ogg_url}")
        return False
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_bytes(r.content)
    return True


def _record_attribution(path: str, info: dict, license_str: str) -> None:
    credits_file = REPO_ROOT / "docs" / "credits-audio.md"
    line = (
        f"| `{path}` | {info['name']} | {info['username']} "
        f"| {license_str} | https://freesound.org/s/{info['id']}/ |\n"
    )
    if not credits_file.exists():
        credits_file.write_text(
            "# Audio Credits\n\nCC-BY files require attribution.\n\n"
            "| File | Title | Author | License | URL |\n"
            "|---|---|---|---|---|\n"
        )
    content = credits_file.read_text()
    if f"/{info['id']}/" not in content:
        with credits_file.open("a") as f:
            f.write(line)
        print(f"  → Attribution recorded in docs/credits-audio.md")


# ── Main ───────────────────────────────────────────────────────────────────────

def main() -> None:
    if not API_KEY:
        print("ERROR: API_KEY is empty.")
        print("Get your free key at: https://freesound.org/apiv2/apply/")
        print("Then fill in API_KEY at the top of this script.")
        sys.exit(1)

    skipped   = []
    succeeded = []
    failed    = []

    # ── Known IDs ──────────────────────────────────────────────────────────────
    print(f"\n{'─'*60}")
    print("Downloading files with known freesound IDs …")
    print(f"{'─'*60}")

    for rel_path, (sound_id, license_str) in KNOWN_IDS.items():
        dest = REPO_ROOT / rel_path
        if not _is_placeholder(dest):
            print(f"  ✓ skip  {rel_path}  (already downloaded)")
            skipped.append(rel_path)
            continue

        print(f"  ↓ {rel_path}  (ID {sound_id})")
        info = _get_sound_info(sound_id)
        if info is None:
            failed.append(rel_path)
            continue

        if _download_preview(info, dest):
            size_kb = dest.stat().st_size // 1024
            print(f"       ✓ {info['name']} by {info['username']}"
                  f"  [{size_kb} KB, {info['duration']:.1f}s, {license_str}]")
            succeeded.append(rel_path)
            if "CC-BY" in license_str:
                _record_attribution(rel_path, info, license_str)
        else:
            failed.append(rel_path)

    # ── Search queries ─────────────────────────────────────────────────────────
    print(f"\n{'─'*60}")
    print("Searching and downloading remaining SFX …")
    print(f"{'─'*60}")

    for rel_path, (query, max_dur) in SEARCH_QUERIES.items():
        dest = REPO_ROOT / rel_path
        if not _is_placeholder(dest):
            print(f"  ✓ skip  {rel_path}  (already downloaded)")
            skipped.append(rel_path)
            continue

        print(f"  ↓ {rel_path}  (search: '{query}', max {max_dur}s)")
        info = _search_sound(query, max_dur)
        if info is None:
            failed.append(rel_path)
            continue

        if _download_preview(info, dest):
            size_kb = dest.stat().st_size // 1024
            print(f"       ✓ {info['name']} by {info['username']}"
                  f"  [{size_kb} KB, {info['duration']:.1f}s, {info['license']}]")
            succeeded.append(rel_path)
        else:
            failed.append(rel_path)

    # ── Summary ────────────────────────────────────────────────────────────────
    print(f"\n{'─'*60}")
    print(f"Done.  ✓ {len(succeeded)} downloaded"
          f"  · ↷ {len(skipped)} skipped"
          f"  · ✗ {len(failed)} failed")
    if failed:
        print("\nFailed files (fix manually):")
        for f in failed:
            print(f"  {f}")
    print(f"{'─'*60}\n")


if __name__ == "__main__":
    main()
