"""Voz ElevenLabs — o topo da corrente de narração, quando há chave.

── POR QUE ELA EXISTE ────────────────────────────────────────────────────
O usuário ouviu a diferença: a narração que ele aprovou nos vídeos veio do
ElevenLabs, e nenhum motor local chega perto. Este módulo coloca essa voz
DENTRO do estúdio: com a chave configurada, toda narração — do player, do
editor e do pré-render em arquivo — sai neural de verdade. Sem chave, nada
muda: a corrente continua (Piper → Voicebox → Google) como sempre.

── A CHAVE ───────────────────────────────────────────────────────────────
Duas formas, nesta ordem:
  1. variável de ambiente ELEVENLABS_API_KEY
  2. arquivo ~/.primocode/elevenlabs.key (uma linha, só a chave)
O endpoint POST /api/voz/elevenlabs grava o arquivo pela interface.

── AS VOZES ──────────────────────────────────────────────────────────────
Catálogo enxuto das vozes prontas da casa (IDs públicos e estáveis), com
o timbre descrito honestamente. A padrão é a Brian — grave, de locução —
porque abertura de vídeo pede peso. Todas falam português no modelo
multilingual. `doc.voz.voz` escolhe pelo id daqui OU por um voice_id cru
do ElevenLabs (quem tem voz clonada usa o ID dela direto).
"""

import json
import os
import urllib.request

ARQUIVO_CHAVE = os.path.join(os.path.expanduser("~"), ".primocode", "elevenlabs.key")
API = "https://api.elevenlabs.io/v1"
MODELO = "eleven_multilingual_v2"
# mp3_44100_128 é CBR: a duração sai do tamanho (bytes/16000), sem depender
# de biblioteca de áudio para ler metadados.
FORMATO = "mp3_44100_128"
BYTES_POR_SEGUNDO = 16000.0

CATALOGO = [
    {"id": "brian",  "nome": "Brian",  "tom": "grave, de locução — é a padrão", "grave": True,
     "voice_id": "nPczCjzI2devNBz1zQrb"},
    {"id": "daniel", "nome": "Daniel", "tom": "grave, de noticiário", "grave": True,
     "voice_id": "onwK4e9ZLuTAKqWW03F9"},
    {"id": "bill",   "nome": "Bill",   "tom": "maduro, acolhedor",
     "voice_id": "pqHfZKP75CvOlQylNhV4"},
    {"id": "liam",   "nome": "Liam",   "tom": "jovem, energia de redes sociais",
     "voice_id": "TX3LPaxmHKxFdv7VOQHJ"},
    {"id": "adam",   "nome": "Adam",   "tom": "firme, dominante", "grave": True,
     "voice_id": "pNInz6obpgDQGcFmaJgB"},
    {"id": "sarah",  "nome": "Sarah",  "tom": "feminina, suave e profissional",
     "voice_id": "EXAVITQu4vr4xnSDxMaL"},
    {"id": "alice",  "nome": "Alice",  "tom": "feminina, clara e confiante",
     "voice_id": "Xb7hH8MSUJpSbSDYk0k2"},
]
POR_ID = {v["id"]: v for v in CATALOGO}
PADRAO = "brian"


def chave():
    k = (os.environ.get("ELEVENLABS_API_KEY") or "").strip()
    if k:
        return k
    try:
        with open(ARQUIVO_CHAVE, encoding="utf-8") as fh:
            return fh.read().strip() or None
    except OSError:
        return None


def configurado():
    return bool(chave())


def gravar_chave(k):
    """Grava (ou apaga, com string vazia) a chave da interface."""
    os.makedirs(os.path.dirname(ARQUIVO_CHAVE), exist_ok=True)
    k = str(k or "").strip()
    if not k:
        try:
            os.remove(ARQUIVO_CHAVE)
        except OSError:
            pass
        return {"ok": True, "configurado": False}
    with open(ARQUIVO_CHAVE, "w", encoding="utf-8") as fh:
        fh.write(k)
    try:
        os.chmod(ARQUIVO_CHAVE, 0o600)      # a chave é segredo do usuário
    except OSError:
        pass
    return {"ok": True, "configurado": True}


def _voice_id(pedido):
    """id do catálogo, voice_id cru do ElevenLabs, ou a padrão."""
    p = str(pedido or "").strip()
    if p.lower() in POR_ID:
        return POR_ID[p.lower()]["voice_id"]
    # voice_id cru: 20 caracteres alfanuméricos é a cara dos IDs deles —
    # deixa passar qualquer coisa nesse formato (vozes clonadas do usuário).
    if len(p) >= 16 and p.isalnum():
        return p
    return POR_ID[PADRAO]["voice_id"]


def sintetizar(texto, voz_cfg=None, timeout=90):
    """Texto → (bytes de MP3, duração em segundos) — ou (None, None).

    None manda a corrente seguir para o próximo motor; nunca levanta para o
    chamador, porque narração com um motor a menos ainda é narração.
    """
    k = chave()
    limpo = " ".join(str(texto or "").split())
    if not k or not limpo:
        return None, None
    voz_cfg = voz_cfg if isinstance(voz_cfg, dict) else {}
    url = "%s/text-to-speech/%s?output_format=%s" % (
        API, _voice_id(voz_cfg.get("voz")), FORMATO)
    corpo = json.dumps({
        "text": limpo[:4900],
        "model_id": MODELO,
        "voice_settings": {"stability": 0.5, "similarity_boost": 0.75},
    }).encode("utf-8")
    req = urllib.request.Request(url, data=corpo, headers={
        "xi-api-key": k,
        "Content-Type": "application/json",
    })
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            dados = r.read()
        if not dados or len(dados) < 400:
            return None, None
        return dados, round(len(dados) / BYTES_POR_SEGUNDO, 2)
    except Exception:
        return None, None


def estado():
    return {
        "ativo": configurado(),
        "vozes": [{"id": v["id"], "nome": v["nome"], "tom": v["tom"],
                   "grave": bool(v.get("grave"))} for v in CATALOGO],
        "padrao": PADRAO,
        "como_configurar": "POST /api/voz/elevenlabs {\"chave\":\"...\"} "
                           "ou variável ELEVENLABS_API_KEY",
    }
