#!/usr/bin/env python3
"""Lädt die für das jamulix-Deployment benötigten Piper-Stimmen herunter.

Prüft welche Dateien bereits vorhanden sind und lädt nur fehlende nach.

Aufruf:
    python scripts/download_voices.py
    python scripts/download_voices.py --voices-dir /pfad/zu/voices
    python scripts/download_voices.py --force   # vorhandene überschreiben
"""
import argparse
import sys
import urllib.request
from pathlib import Path

BASE_URL = "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0"

# Alle für dieses Deployment benötigten Piper-Stimmen.
# Abgeleitet aus PIPER_VOICE_GENDERED + LANG_TO_PIPER_VOICE in app/dependencies.py.
# Bei Änderungen dort diese Liste synchron halten.
# Stimmen mit '#speaker_id'-Suffix (ES sharvard) sind Multi-Speaker-Modelle —
# nur eine Datei für beide Geschlechter, kein separater Download nötig.
REQUIRED_VOICES = [
    "de_DE-thorsten-high",    # DE ♂
    "de_DE-kerstin-low",      # DE ♀
    "en_US-lessac-high",      # EN ♂
    "en_US-amy-medium",       # EN ♀
    "fr_FR-siwis-medium",     # FR ♀
    "fr_FR-tom-medium",       # FR ♂
    "es_ES-sharvard-medium",  # ES ♂+♀  (Multi-Speaker: voice#0=♂, voice#1=♀)
    "it_IT-paola-medium",     # IT ♀
    "it_IT-riccardo-x_low",   # IT ♂
    "pt_BR-faber-medium",     # PT ♂    (kein ♀ in piper-voices verfügbar)
    "pl_PL-gosia-medium",     # PL ♀
    "pl_PL-darkman-medium",   # PL ♂
    "ar_JO-kareem-medium",    # AR ♂    (kein ♀ in piper-voices verfügbar)
    "ru_RU-irina-medium",     # RU ♀
    "ru_RU-ruslan-medium",    # RU ♂
    "zh_CN-huayan-medium",    # ZH ♀    (kein ♂ in piper-voices verfügbar)
]

_QUALITY_SUFFIXES = {"high", "medium", "low", "x_low"}


def _parse_voice_name(name: str) -> tuple[str, str, str, str]:
    """Zerlegt z. B. 'de_DE-kerstin-low' → (lang_family, lang_code, speaker, quality)."""
    name = name.split("#")[0]
    parts = name.split("-")
    lang_code = parts[0]                          # z. B. "de_DE"
    lang_family = lang_code.split("_")[0].lower() # z. B. "de"
    # Qualität kann aus zwei Teilen bestehen (x_low)
    if len(parts) >= 3 and f"{parts[-2]}_{parts[-1]}" in _QUALITY_SUFFIXES:
        quality = f"{parts[-2]}_{parts[-1]}"      # z. B. "x_low"
        speaker = "-".join(parts[1:-2])
    else:
        quality = parts[-1]                       # z. B. "high"
        speaker = "-".join(parts[1:-1])
    return lang_family, lang_code, speaker, quality


def _hf_url(voice_name: str, ext: str) -> str:
    lang_family, lang_code, speaker, quality = _parse_voice_name(voice_name)
    path = f"{lang_family}/{lang_code}/{speaker}/{quality}/{voice_name}{ext}"
    return f"{BASE_URL}/{path}"


def _download(url: str, dest: Path) -> None:
    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp = dest.with_suffix(dest.suffix + ".tmp")

    def _progress(block_num, block_size, total_size):
        if total_size > 0:
            pct = min(100, block_num * block_size * 100 // total_size)
            bar = "█" * (pct // 5) + "░" * (20 - pct // 5)
            mb = total_size / 1_048_576
            print(f"\r    [{bar}] {pct:3d}%  ({mb:.0f} MB)", end="", flush=True)

    try:
        urllib.request.urlretrieve(url, tmp, _progress)
        tmp.rename(dest)
        print(f"\r    ✓  {dest.name}            ")
    except Exception:
        tmp.unlink(missing_ok=True)
        raise


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--voices-dir", default="config/voices",
                        help="Zielverzeichnis (Standard: config/voices)")
    parser.add_argument("--force", action="store_true",
                        help="Vorhandene Dateien überschreiben")
    args = parser.parse_args()

    voices_dir = Path(args.voices_dir)

    to_download = []
    already_present = []
    for voice in REQUIRED_VOICES:
        onnx  = voices_dir / f"{voice}.onnx"
        json_ = voices_dir / f"{voice}.onnx.json"
        if args.force or not onnx.exists() or not json_.exists():
            to_download.append(voice)
        else:
            already_present.append(voice)

    print(f"Stimmen-Verzeichnis : {voices_dir.resolve()}")
    print(f"Bereits vorhanden   : {len(already_present)}/{len(REQUIRED_VOICES)}")
    print(f"Herunterzuladen     : {len(to_download)}")

    if not to_download:
        print("\nAlle Stimmen sind bereits vorhanden.")
        return

    print()
    errors: list[tuple[str, str, str]] = []
    for i, voice in enumerate(to_download, 1):
        print(f"[{i}/{len(to_download)}] {voice}")
        for ext in (".onnx", ".onnx.json"):
            url  = _hf_url(voice, ext)
            dest = voices_dir / f"{voice}{ext}"
            if dest.exists() and not args.force:
                print(f"    ✓  {dest.name} (vorhanden)")
                continue
            try:
                _download(url, dest)
            except Exception as e:
                print(f"\n    ✗  FEHLER: {e}")
                errors.append((voice, ext, str(e)))

    print()
    if errors:
        print(f"{len(errors)} Fehler beim Download:")
        for voice, ext, msg in errors:
            print(f"  {voice}{ext}: {msg}")
        sys.exit(1)
    else:
        print(f"Fertig — {len(to_download)} Stimme(n) heruntergeladen.")


if __name__ == "__main__":
    main()
