#!/usr/bin/env python3
"""
Regeneriert die Audios fuer eine oder mehrere Heli-Missionen via ElevenLabs.

Welche Audios pro Mission:
  r_mission_intro_<mid>   (Sarah/Tower)
  r_mission_geo_<mid>     (Bella/Briefing)
  r_mission_start_<mid>   (Sarah/Tower)
  r_tower_geo_<mid>       (Sarah/Tower)
  r_wp_geo_<wpkey>        (Bella/Briefing)  fuer jeden Wegpunkt der Mission

Aussprache-Map wird auf TTS-Input angewendet (data/aussprache-map.json).
Cache-Eintraege in audio-durations.json werden geloescht (neu gemessen beim
naechsten Tool-Load).

Usage:
  python regenerate-mission-audios.py m2
  python regenerate-mission-audios.py m2 m3 m4
  python regenerate-mission-audios.py all
  python regenerate-mission-audios.py m2 --skip-existing   # nur fehlende
"""
import json, os, sys, subprocess, tempfile, shutil

KEY = os.environ.get('ELEVENLABS_API_KEY') or 'sk_8e21b8c2723d95fc581953c1d0fff9af3e1298faa378a651'
REPO = 'C:/xampp/htdocs/geograsim/App/sims/heli'
OUT_DIR = REPO + '/sounds/radio'
TEXTS_FILE = REPO + '/scripts/audio-texts.json'
MISSIONS_FILE = REPO + '/data/missions.json'
MAP_FILE = REPO + '/data/aussprache-map.json'
DUR_FILE = REPO + '/data/audio-durations.json'

VOICE_SARAH = 'EXAVITQu4vr4xnSDxMaL'  # Tower-Lotsin
VOICE_BELLA = 'hpp4J3VqNfWAUOO0d1Us'  # Pilotin/Briefing

def voice_for(aid):
    if aid.startswith('r_mission_intro_') or aid.startswith('r_mission_start_') or aid.startswith('r_tower_'):
        return VOICE_SARAH, 'Sarah'
    return VOICE_BELLA, 'Bella'

def apply_pronunciation(text, mapping):
    # Laengere Keys zuerst, damit 'Brandnertal' vor 'Brandner' greift
    for k in sorted(mapping.keys(), key=len, reverse=True):
        text = text.replace(k, mapping[k])
    return text

def generate_one(aid, text, voice_id, skip_existing=False):
    out = OUT_DIR + '/' + aid + '.mp3'
    if skip_existing and os.path.exists(out):
        return 'skip-existing', 0
    if os.path.exists(out):
        shutil.copy(out, out + '.bak-pre-regen')
    body = json.dumps({
        'text': text,
        'model_id': 'eleven_multilingual_v2',
        'voice_settings': {'stability': 0.55, 'similarity_boost': 0.75, 'style': 0.1, 'use_speaker_boost': True}
    }, ensure_ascii=False)
    with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', suffix='.json', delete=False) as f:
        f.write(body); bpath = f.name
    r = subprocess.run(['curl','-sS','-w','%{http_code}','-o',out,
        '-X','POST','https://api.elevenlabs.io/v1/text-to-speech/' + voice_id,
        '-H','xi-api-key: ' + KEY,
        '-H','Content-Type: application/json; charset=utf-8',
        '--data-binary','@' + bpath], capture_output=True, text=True, timeout=90)
    os.unlink(bpath)
    if r.stdout.strip() == '200':
        return 'ok', os.path.getsize(out)
    return 'err-http-' + r.stdout, 0

def audios_for_mission(mission, texts):
    """Liefert Liste der Audio-IDs, die fuer eine Mission existieren sollen."""
    mid = mission['id']
    wps = mission.get('waypoints', [])
    ids = [
        'r_mission_intro_' + mid,
        'r_mission_geo_' + mid,
        'r_mission_start_' + mid,
        'r_tower_geo_' + mid,
    ]
    for w in wps:
        ids.append('r_wp_geo_' + w)
    # Filtere existierende
    return [aid for aid in ids if aid in texts]

def main():
    args = sys.argv[1:]
    if not args:
        print(__doc__)
        sys.exit(1)
    skip_existing = '--skip-existing' in args
    args = [a for a in args if not a.startswith('--')]

    texts = json.load(open(TEXTS_FILE, 'r', encoding='utf-8'))
    missions = json.load(open(MISSIONS_FILE, 'r', encoding='utf-8'))['missions']
    pron_map = json.load(open(MAP_FILE, 'r', encoding='utf-8'))['pronunciations']

    if 'all' in args:
        targets = missions
    else:
        targets = [m for m in missions if m['id'] in args]
        if not targets:
            print('Keine Mission gefunden fuer: ' + ', '.join(args))
            sys.exit(2)

    durj = json.load(open(DUR_FILE, 'r', encoding='utf-8'))
    total_ok = 0; total_fail = 0; total_skip = 0
    for mission in targets:
        mid = mission['id']
        aids = audios_for_mission(mission, texts)
        print(f'\n=== {mid} ({mission.get("title","?")}) — {len(aids)} Audios ===')
        for aid in aids:
            text = texts[aid]
            tts_text = apply_pronunciation(text, pron_map)
            voice, label = voice_for(aid)
            status, size = generate_one(aid, tts_text, voice, skip_existing)
            if status == 'ok':
                print(f'  OK  {aid:36s} {size//1024:>5}K  [{label}]')
                total_ok += 1
                durj['durations'].pop(aid, None)  # cache leeren
            elif status == 'skip-existing':
                print(f'  --  {aid:36s}     skip (existiert schon)')
                total_skip += 1
            else:
                print(f'  ERR {aid:36s}  {status}')
                total_fail += 1
    json.dump(durj, open(DUR_FILE, 'w', encoding='utf-8'), indent=2, ensure_ascii=False)
    print(f'\nGesamt: {total_ok} generiert, {total_skip} uebersprungen, {total_fail} Fehler')

if __name__ == '__main__':
    main()
