"""TTS (Sprachausgabe).

Primär: ElevenLabs (eleven_v3, explizit Serbisch) mit der gewählten Stimme.
Fallback: edge-tts (kostenlos), falls ElevenLabs ausfällt — App bleibt funktionsfähig.
Cache by Hash über Engine+Stimme+Modell+Text → identische Antworten werden wiederverwendet.
"""
from __future__ import annotations

import asyncio
import hashlib
from pathlib import Path

import edge_tts

from . import config, eleven


def _clean(text: str) -> str:
    for ch in ("*", "_", "#", "`", "\"", "(", ")", "[", "]"):
        text = text.replace(ch, " ")
    return " ".join(text.split()).strip()


async def _edge_fallback(text: str, out: Path) -> None:
    comm = edge_tts.Communicate(text, config.PLAPPI_VOICE,
                                rate=config.PLAPPI_TTS_RATE, pitch=config.PLAPPI_TTS_PITCH)
    await comm.save(str(out))


async def synthesize(text: str, voice: str | None = None) -> str:
    """-> relativer Audio-Dateiname (unter data/audio)."""
    clean = _clean(text) or "Hallo."
    use_eleven = bool(config.ELEVENLABS_API_KEY)
    voice_id = voice or config.ELEVENLABS_VOICE_ID
    tag = f"el|{voice_id}|{config.ELEVENLABS_MODEL}" if use_eleven else f"edge|{config.PLAPPI_VOICE}"
    key = hashlib.md5(f"{tag}|{clean}".encode()).hexdigest()[:16]
    out = config.AUDIO_DIR / f"{key}.mp3"
    if out.exists() and out.stat().st_size > 200:
        return out.name

    if use_eleven:
        try:
            loop = asyncio.get_event_loop()
            audio = await loop.run_in_executor(None, lambda: eleven.tts(clean, voice_id=voice_id))
            out.write_bytes(audio)
            return out.name
        except Exception as e:  # noqa: BLE001 — bewusst breit, dann Fallback
            print(f"[tts] ElevenLabs fehlgeschlagen ({e}) -> edge-tts Fallback", flush=True)

    await _edge_fallback(clean, out)
    return out.name
