#!/usr/bin/env python3
"""
download-sunrise-sounds.py — download CC-licensed ambient sounds from Freesound
for use as sunrise video fallback audio.

Usage:
    python3 download-sunrise-sounds.py --api-key YOUR_KEY
    python3 download-sunrise-sounds.py --api-key YOUR_KEY --dir /path/to/sunrise-sounds
    python3 download-sunrise-sounds.py --api-key YOUR_KEY --per-category 30

Get a free API key at: https://freesound.org/apiv2/apply/
(Freesound account required — registration is free.)

Files are saved as HQ 128 kbps MP3 previews, which is sufficient for
background ambient audio.  Full-quality downloads require OAuth2; if you want
lossless originals, log into freesound.org and download the files listed in
the manifest this script writes alongside the audio.

Folder structure written:
    <dir>/clear-spring/   dawn chorus, birds, spring morning
    <dir>/clear-summer/   birds, insects, summer morning
    <dir>/clear-autumn/   sparse birds, leaves, autumn morning
    <dir>/clear-winter/   frost silence, sparse birds, winter morning
    <dir>/cloudy/         muffled dawn, overcast ambience
    <dir>/rain/           light rain, drizzle
    <dir>/heavy-rain/     downpour, storm rain
    <dir>/snow/           near-silence, snow ambience
    <dir>/foggy/          mist, fog ambience
    <dir>/thunder/        thunder + rain
    <dir>/windy/          wind, breeze through trees

daily_sunrise_video.sh picks a random file from sunrise-sounds/ as a fallback
when no camera mic recording is available for that day.  A future update will
match the folder to the day's actual weather via the OpenWeatherMap API.

Requires: requests  (pip3 install requests)
"""

import argparse
import json
import os
import random
import sys
import time
from pathlib import Path

try:
    import requests
except ImportError:
    sys.exit("Missing dependency: pip3 install requests")

FREESOUND_API = "https://freesound.org/apiv2"

# Each category maps to a list of search queries tried in order.
# Queries are shuffled per-run so repeated runs fill in different sounds.
CATEGORIES = {
    "clear-spring": [
        "dawn chorus spring birds",
        "spring morning birds outdoor",
        "birds chirping spring sunrise",
        "dawn birds forest spring",
        "bird song spring morning",
    ],
    "clear-summer": [
        "summer dawn birds outdoor",
        "morning birds insects summer",
        "dawn chorus summer",
        "bird song summer morning outdoor",
        "summer sunrise birds",
    ],
    "clear-autumn": [
        "autumn morning birds outdoor",
        "fall dawn birds",
        "autumn bird song morning",
        "sparse birds autumn outdoor",
        "fall morning outdoor ambience",
    ],
    "clear-winter": [
        "winter morning birds outdoor",
        "frost morning quiet outdoor",
        "winter dawn outdoor sparse",
        "quiet winter morning outdoor",
        "winter birds sparse outdoor",
    ],
    "cloudy": [
        "overcast morning outdoor birds",
        "cloudy dawn outdoor ambience",
        "grey morning birds outdoor",
        "morning overcast outdoor",
        "cloudy outdoor morning",
    ],
    "rain": [
        "light rain outdoor",
        "gentle rain leaves",
        "soft rain morning outdoor",
        "drizzle outdoor ambience",
        "rain birds outdoor",
    ],
    "heavy-rain": [
        "heavy rain outdoor",
        "downpour rain",
        "rain storm outdoor",
        "heavy rainfall outdoor",
        "strong rain outdoor",
    ],
    "snow": [
        "snow silence outdoor",
        "winter snow ambience outdoor",
        "quiet snow outdoor",
        "snowfall outdoor",
        "winter silence snow",
    ],
    "foggy": [
        "fog morning outdoor",
        "mist ambience outdoor",
        "foggy morning birds",
        "misty dawn outdoor",
        "fog outdoor ambience",
    ],
    "thunder": [
        "thunder rain outdoor",
        "distant thunder outdoor",
        "thunderstorm outdoor",
        "thunder rumble rain outdoor",
        "thunder lightning rain",
    ],
    "windy": [
        "wind outdoor morning",
        "breeze through trees outdoor",
        "wind trees outdoor",
        "morning wind outdoor",
        "gentle wind outdoor",
    ],
}

# Licenses acceptable for personal use
GOOD_LICENSES = {"Creative Commons 0", "Attribution", "Attribution NonCommercial"}


def search(api_key: str, query: str, min_dur: int, page_size: int = 15) -> list:
    params = {
        "token": api_key,
        "query": query,
        "filter": (
            f"duration:[{min_dur} TO 300] "
            'license:("Creative Commons 0" OR "Attribution" OR "Attribution NonCommercial")'
        ),
        "fields": "id,name,previews,license,duration,username,tags",
        "page_size": page_size,
        "sort": "rating_desc",
    }
    r = requests.get(f"{FREESOUND_API}/search/text/", params=params, timeout=20)
    r.raise_for_status()
    return r.json().get("results", [])


def download_preview(sound: dict, dest_dir: Path) -> Path | None:
    preview_url = sound["previews"].get("preview-hq-mp3")
    if not preview_url:
        return None

    safe = "".join(c if c.isalnum() or c in "-_." else "_" for c in sound["name"])
    filename = f"{sound['id']}-{safe}"
    if not filename.lower().endswith(".mp3"):
        filename += ".mp3"
    dest = dest_dir / filename

    if dest.exists():
        return dest  # already downloaded

    r = requests.get(preview_url, timeout=30, stream=True)
    r.raise_for_status()
    with open(dest, "wb") as f:
        for chunk in r.iter_content(8192):
            f.write(chunk)
    return dest


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Download CC ambient sounds from Freesound for sky-cam sunrise audio"
    )
    parser.add_argument(
        "--api-key", required=True,
        help="Freesound API key — get one free at freesound.org/apiv2/apply/"
    )
    parser.add_argument(
        "--dir", default="sunrise-sounds",
        help="Output directory (default: ./sunrise-sounds)"
    )
    parser.add_argument(
        "--per-category", type=int, default=25,
        help="Target number of files per category (default: 25 → ~275 total)"
    )
    parser.add_argument(
        "--min-duration", type=int, default=12,
        help="Minimum sound duration in seconds (default: 12)"
    )
    parser.add_argument(
        "--categories", nargs="+", metavar="CAT",
        help="Only download these categories (default: all)"
    )
    args = parser.parse_args()

    base_dir = Path(args.dir)
    base_dir.mkdir(parents=True, exist_ok=True)

    categories = args.categories or list(CATEGORIES.keys())
    unknown = set(categories) - set(CATEGORIES.keys())
    if unknown:
        sys.exit(f"Unknown categories: {unknown}\nValid: {list(CATEGORIES.keys())}")

    manifest_path = base_dir / "manifest.json"
    manifest: dict = {}
    if manifest_path.exists():
        with open(manifest_path) as f:
            manifest = json.load(f)

    grand_total = 0

    for category in categories:
        cat_dir = base_dir / category
        cat_dir.mkdir(exist_ok=True)

        existing = list(cat_dir.glob("*.mp3"))
        needed = args.per_category - len(existing)
        if needed <= 0:
            print(f"{category:20s} already has {len(existing)} files — skipping")
            continue

        print(f"\n{category} — need {needed} more (have {len(existing)})")
        queries = CATEGORIES[category][:]
        random.shuffle(queries)

        collected = 0
        seen_ids = {p.name.split("-")[0] for p in existing if p.name[0].isdigit()}
        cat_manifest = manifest.setdefault(category, {})

        for query in queries:
            if collected >= needed:
                break
            print(f"  searching: '{query}'")
            try:
                results = search(args.api_key, query, args.min_duration)
                random.shuffle(results)
                for sound in results:
                    if collected >= needed:
                        break
                    sid = str(sound["id"])
                    if sid in seen_ids:
                        continue
                    seen_ids.add(sid)
                    try:
                        dest = download_preview(sound, cat_dir)
                        if dest:
                            license_short = sound["license"].split("/")[-2] if "/" in sound["license"] else sound["license"]
                            print(
                                f"    {dest.name}  "
                                f"({sound['duration']:.0f}s, {license_short}, "
                                f"by {sound['username']})"
                            )
                            cat_manifest[sid] = {
                                "file": dest.name,
                                "name": sound["name"],
                                "duration": sound["duration"],
                                "license": sound["license"],
                                "username": sound["username"],
                            }
                            collected += 1
                            grand_total += 1
                        time.sleep(0.4)
                    except Exception as e:
                        print(f"    skipped {sid}: {e}")
            except Exception as e:
                print(f"  search error: {e}")
            time.sleep(1.0)

        total_now = len(existing) + collected
        print(f"  {category}: {collected} downloaded → {total_now} total")

    with open(manifest_path, "w") as f:
        json.dump(manifest, f, indent=2)
    print(f"\n{grand_total} new files downloaded to {base_dir}/")
    print(f"Manifest written: {manifest_path}")
    print()
    print("Next steps:")
    print(f"  1. Set AUDIO_ENABLED=true in sky-cam.conf")
    print(f"  2. Verify sounds with: ls {base_dir}/*/ | head -40")
    print(f"  3. Re-run with --per-category 40 any time to add more variety")


if __name__ == "__main__":
    main()
