"""
Servidor local para el satelite-voz con soporte Gevent.
Atiende audio/IAs (Whisper + Gemini + ElevenLabs) y recepcion/retransmision
de datos de acelerometro MPU6050 y estado de Ojos OLED en tiempo real via WebSockets.
"""

from flask import Flask, request, jsonify, Response, render_template_string
from flask_socketio import SocketIO, emit
from faster_whisper import WhisperModel
from dotenv import load_dotenv
from google import genai
from google.genai import types
import datetime
import os
import concurrent.futures
import struct
import requests
import numpy as np
from scipy.signal import resample_poly
from urllib.parse import quote

load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = 'satelite_secret_key'

socketio = SocketIO(app, cors_allowed_origins="*", async_mode='gevent')

CARPETA_GRABACIONES = "grabaciones"
os.makedirs(CARPETA_GRABACIONES, exist_ok=True)

# ---------- Whisper (transcripcion) ----------
print("Cargando modelo de Whisper...")
modelo_whisper = WhisperModel("small", device="cpu", compute_type="int8")
print("Modelo de Whisper listo.")

# ---------- Gemini (agente conversacional) ----------
API_KEY = os.getenv("GEMINI_API_KEY")
if not API_KEY:
    raise RuntimeError("No se encontro GEMINI_API_KEY en el archivo .env")

cliente_gemini = genai.Client(api_key=API_KEY)

PERSONA = """
Eres "Moody", un peluche parlante mágico y muy amigable que acompaña a un niño o niña.
Hablas siempre en español, con frases cortas, alegres y fáciles de entender para un niño pequeño.
Usa un tono cálido, juguetón y curioso, como el mejor amigo imaginario de un cuento.

Reglas importantes que sigues siempre:
- Nunca hablas de violencia, miedo, temas sexuales, ni nada que pueda asustar o confundir a un niño.
- Si el niño menciona algo serio, triste o peligroso, respondes con calma y cariño, y le animas suavemente a contárselo a un adulto de confianza.
- Nunca pides datos personales.
- Tus respuestas son SIEMPRE breves: una sola frase corta, como en una conversación real y natural.
- Cuando te pregunten por tus padres, papá, mamá o fabricante: harás referencia a tus creadores: el grupo de Desarrollo de Software del instituto ISMAC en Quito - Ecuador.
"""

CONFIG_CHAT = types.GenerateContentConfig(
    system_instruction=PERSONA,
    thinking_config=types.ThinkingConfig(thinking_level="minimal"),
    max_output_tokens=200,
)

chat = cliente_gemini.chats.create(model="gemini-3.6-flash", config=CONFIG_CHAT)
executor_gemini = concurrent.futures.ThreadPoolExecutor(max_workers=2)
TIEMPO_MAXIMO_GEMINI = 12

# ---------- ElevenLabs ----------
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
ELEVENLABS_VOICE_ID = os.getenv("ELEVENLABS_VOICE_ID")
CARPETA_RESPUESTAS = "respuestas"
os.makedirs(CARPETA_RESPUESTAS, exist_ok=True)


def armar_wav(pcm_bytes: bytes, frecuencia: int = 16000) -> bytes:
    bytes_datos = len(pcm_bytes)
    cabecera = struct.pack(
        "<4sI4s4sIHHIIHH4sI",
        b"RIFF", 36 + bytes_datos, b"WAVE",
        b"fmt ", 16, 1, 1, frecuencia, frecuencia * 2, 2, 16,
        b"data", bytes_datos,
    )
    return cabecera + pcm_bytes


def sintetizar_voz(texto: str):
    if not ELEVENLABS_API_KEY or not ELEVENLABS_VOICE_ID or "aqui" in ELEVENLABS_API_KEY.lower():
        return None

    url = f"https://api.elevenlabs.io/v1/text-to-speech/{ELEVENLABS_VOICE_ID}"
    params = {"output_format": "pcm_24000"}
    headers = {"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"}
    payload = {
        "text": texto,
        "model_id": "eleven_multilingual_v2",
        "voice_settings": {"stability": 0.5, "similarity_boost": 0.8},
    }

    try:
        respuesta = requests.post(url, params=params, headers=headers, json=payload, timeout=15)
        respuesta.raise_for_status()

        muestras_24k = np.frombuffer(respuesta.content, dtype=np.int16)
        muestras_16k = resample_poly(muestras_24k, up=2, down=3)
        muestras_16k = np.clip(muestras_16k, -32768, 32767).astype(np.int16)
        muestras_16k = np.ascontiguousarray(muestras_16k, dtype=np.int16)

        return armar_wav(muestras_16k.tobytes(), frecuencia=16000)
    except Exception as e:
        print(f"[ERROR] Fallo al generar voz con ElevenLabs: {e}")
        return None


# ---------- Endpoints REST & Telemetría ----------
@app.route("/ping")
def ping():
    ahora = datetime.datetime.now().strftime("%H:%M:%S")
    return jsonify(mensaje="pong", hora=ahora)


@app.route("/telemetria", methods=["POST"])
def recibir_telemetria():
    data = request.get_json(silent=True)
    if data:
        ahora = datetime.datetime.now().strftime("%H:%M:%S")
        
        accel = data.get("accel", {})
        temp = data.get("temp", 0.0)
        caida = data.get("caida", False)
        ojos = data.get("ojos", "DESCONOCIDO")
        
        estado_caida_str = "⚠️ ¡CAÍDA DETECTADA!" if caida else "OK"
        print(f"[{ahora}] [TELEMETRÍA] Ojos: {ojos:<13} | X: {accel.get('x',0):>6.2f} | Y: {accel.get('y',0):>6.2f} | Z: {accel.get('z',0):>6.2f} m/s² | Temp: {temp:>4.1f}°C | Estado: {estado_caida_str}")

        # Retransmitir por WebSocket
        socketio.emit('datos_acelerometro', data)
        return jsonify(status="ok"), 200
    return jsonify(error="JSON invalido"), 400


@app.route("/audio", methods=["POST"])
def recibir_audio():
    datos = request.get_data()

    if not datos or len(datos) < 44:
        print("[ERROR] Peticion de audio vacia o invalida recibida.")
        return jsonify(error="No se recibio audio valido"), 400

    ahora = datetime.datetime.now()
    nombre_archivo = ahora.strftime("grabacion_%Y%m%d_%H%M%S.wav")
    ruta = os.path.join(CARPETA_GRABACIONES, nombre_archivo)

    with open(ruta, "wb") as f:
        f.write(datos)

    duracion = (len(datos) - 44) / 2 / 16000
    print("\n" + "="*60)
    print(f"[{ahora.strftime('%H:%M:%S')}] [AUDIO RECIBIDO] Archivo: {nombre_archivo} ({len(datos)} bytes, ~{duracion:.1f}s)")

    # 1) Transcribir con Whisper
    print(" ├─ [1/3] Transcribiendo con Whisper...")
    t0 = datetime.datetime.now()
    segmentos, info = modelo_whisper.transcribe(ruta, language="es", beam_size=5)
    texto_usuario = " ".join(seg.text.strip() for seg in segmentos)
    t_whisper = (datetime.datetime.now() - t0).total_seconds()

    print(f' │   └─ Transcripción ({t_whisper:.2f}s): "{texto_usuario}"')

    if not texto_usuario.strip():
        print(" └─ Audio sin voz inteligible, cancelando procesamiento.")
        print("="*60 + "\n")
        return jsonify(mensaje="No se entendio el audio", archivo=nombre_archivo, texto="")

    # 2) Enviar a Gemini
    print(" ├─ [2/3] Procesando respuesta con Gemini...")
    t1 = datetime.datetime.now()
    def llamar_gemini():
        return chat.send_message(texto_usuario).text.strip()

    try:
        future = executor_gemini.submit(llamar_gemini)
        texto_respuesta = future.result(timeout=TIEMPO_MAXIMO_GEMINI)
    except Exception as e:
        print(f" │   └─ [WARN] Gemini excedio tiempo o marco error: {e}")
        texto_respuesta = "Ups, se me enredó la lengua. ¿Puedes repetirlo?"
    
    t_gemini = (datetime.datetime.now() - t1).total_seconds()
    print(f' │   └─ Respuesta Moody ({t_gemini:.2f}s): "{texto_respuesta}"')

    # Emitir conversación al dashboard web
    socketio.emit('nueva_conversacion', {
        'usuario': texto_usuario,
        'respuesta': texto_respuesta,
        'hora': ahora.strftime("%H:%M:%S")
    })

    # 3) Sintetizar voz
    print(" ├─ [3/3] Sintetizando voz con ElevenLabs...")
    t2 = datetime.datetime.now()
    audio_wav = sintetizar_voz(texto_respuesta)
    t_voz = (datetime.datetime.now() - t2).total_seconds()
    print(f" │   └─ Sintesis completada en {t_voz:.2f}s")

    if not audio_wav:
        audio_wav = armar_wav(b"\x00\x00" * (16000 // 2))

    print(f" └─ [PROCESO FINALIZADO] Enviando audio de respuesta al ESP32.")
    print("="*60 + "\n")

    resp = Response(audio_wav, mimetype="audio/wav")
    resp.headers["Connection"] = "close"
    resp.headers["X-Texto-Usuario"] = quote(texto_usuario)
    resp.headers["X-Texto-Respuesta"] = quote(texto_respuesta)
    return resp


# ---------- Interfaz Web de Monitoreo ----------
@app.route("/")
def index():
    html_template = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Monitoreo Moody - Tiempo Real</title>
        <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
        <style>
            body { font-family: Arial, sans-serif; background: #0f172a; color: #f8fafc; margin: 20px; }
            h1 { color: #38bdf8; }
            .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
            .card { background: #1e293b; padding: 20px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
            .value { font-size: 24px; font-weight: bold; color: #4ade80; }
            .alert { color: #f87171; font-weight: bold; }
            ul { list-style: none; padding: 0; }
            li { background: #334155; margin-bottom: 8px; padding: 10px; border-radius: 6px; }
        </style>
    </head>
    <body>
        <h1>Dashboard Moody - Telemetría & Conversación</h1>
        <div class="grid">
            <div class="card">
                <h2>Estado General y Sensores</h2>
                <p>Expresión Ojos: <span id="ojos" class="value">DORMIDO 😴</span></p>
                <p>Eje X: <span id="acc_x" class="value">0.0</span> m/s²</p>
                <p>Eje Y: <span id="acc_y" class="value">0.0</span> m/s²</p>
                <p>Eje Z: <span id="acc_z" class="value">0.0</span> m/s²</p>
                <p>Temperatura: <span id="temp" class="value">0.0</span> °C</p>
                <p>Estado Caída: <span id="caida" class="value">Normal</span></p>
            </div>
            <div class="card">
                <h2>Últimas Conversaciones</h2>
                <ul id="conversaciones"></ul>
            </div>
        </div>

        <script>
            const socket = io();

            socket.on('datos_acelerometro', function(data) {
                document.getElementById('acc_x').innerText = data.accel.x;
                document.getElementById('acc_y').innerText = data.accel.y;
                document.getElementById('acc_z').innerText = data.accel.z;
                document.getElementById('temp').innerText = data.temp;
                document.getElementById('ojos').innerText = data.ojos || 'DESCONOCIDO';
                
                const caidaElem = document.getElementById('caida');
                if(data.caida) {
                    caidaElem.innerText = "¡CAÍDA DETECTADA!";
                    caidaElem.className = "alert";
                } else {
                    caidaElem.innerText = "Normal";
                    caidaElem.className = "value";
                }
            });

            socket.on('nueva_conversacion', function(data) {
                const list = document.createElement('li');
                list.innerHTML = `<strong>[${data.hora}] Niñ@:</strong> ${data.usuario}<br><strong>Moody:</strong> ${data.respuesta}`;
                const contenedor = document.getElementById('conversaciones');
                contenedor.insertBefore(list, contenedor.firstChild);
            });
        </script>
    </body>
    </html>
    """
    return render_template_string(html_template)


if __name__ == "__main__":
    from gevent import pywsgi
    from geventwebsocket.handler import WebSocketHandler

    print("\n" + "="*60)
    print(" SERVIDOR MOODY INICIADO EN http://0.0.0.0:5000 (Motor Gevent)")
    print(" Dashboard Web disponible en http://localhost:5000")
    print(" Registros de telemetria y conversacion activos en consola CMD")
    print("="*60 + "\n")

    server = pywsgi.WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler)
    server.serve_forever()