#!/usr/bin/env python3
"""
Listener generador de eventos de prueba basado en datos reales en MySQL.
- Procesa lazos en orden secuencial (1, 2, 3, ...)
- Obtiene los primeros 20 elementos (sensores y módulos) de cada lazo
- Genera solo eventos de PRUEBA para esos elementos
- Envía cada evento al endpoint Laravel /api/central/evento
- En modo loop: reinicia desde el lazo 1 después del último lazo
- En modo una_vez: procesa todos los lazos una sola vez

Requisitos:
  pip install requests PyMySQL

Uso:
  python listeners/listener_mysql_generador.py --revision 1 --url http://localhost:8000 --intervalo 3 --limite 20 --modo una_vez
"""

import argparse
import datetime
import os
import random
import sys
import time
from typing import Any, Dict, List, Optional

import pymysql
import requests

# Valores por defecto (se pueden sobreescribir por CLI)
DEFAULT_SERVER_URL = "http://localhost:8000"
DEFAULT_ENDPOINT_EVENTO = "/api/central/evento"
DEFAULT_REVISION_ID = 1
DEFAULT_INTERVALO = 6
DEFAULT_LIMITE = 10
DEFAULT_TIMEOUT = 8
DEFAULT_JITTER = 3.0

# Lectura opcional de credenciales desde variables de entorno (.env cargado por el sistema si procede)
DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
DB_PORT = int(os.getenv("DB_PORT", "3306"))
DB_DATABASE = os.getenv("DB_DATABASE", "sitelec")
DB_USERNAME = os.getenv("DB_USERNAME", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "")
DB_CHARSET = os.getenv("DB_CHARSET", "utf8mb4")

# Mapeo de abreviaturas a tipo de dispositivo presentado en el texto del evento
ABREVIATURA_TO_TIPO = {
    "OPTI": "OPTI",
    "TER": "TER",
    "MULT": "OPTI",
    "SMART 4": "OPTI",
    "PUL": "PUL",
    "SIRE": "SIR",
    "MON": "MOD",
    "CON": "MOD",
    "AUX": "MOD",
    "ASPR": "MOD",
    "SPRK": "MOD",
    "ZMX": "MOD",
    "CDI": "MOD",
    "BOOSTER": "MOD",
    "BIES": "BIES",
}


def conectar_mysql():
    return pymysql.connect(
        host=DB_HOST,
        port=DB_PORT,
        user=DB_USERNAME,
        password=DB_PASSWORD,
        database=DB_DATABASE,
        charset=DB_CHARSET,
        cursorclass=pymysql.cursors.DictCursor,
        autocommit=True,
    )


def obtener_lazos_ordenados(revision_id: int) -> List[int]:
    """Obtiene todos los lazos con detectores ordenados por número."""
    sql = """
        SELECT DISTINCT l.numero AS lazo_numero
        FROM ci_revision_elementos e
        JOIN ci_revision_centrales c ON c.id = e.id_central
        LEFT JOIN ci_revision_lazos l ON l.id = e.lazo
        LEFT JOIN ci_revision_tipo_elementos te ON te.id = e.tipo
        WHERE c.revision_id = %s 
        AND l.numero IS NOT NULL
        ORDER BY l.numero ASC
    """
    
    conn = conectar_mysql()
    try:
        with conn.cursor() as cur:
            cur.execute(sql, (revision_id,))
            rows = cur.fetchall()
            return [row['lazo_numero'] for row in rows] if rows else []
    finally:
        conn.close()


def obtener_detectores_lazo(revision_id: int, lazo_numero: int, limite: int = 20) -> List[Dict[str, Any]]:
    """Obtiene detectores de un lazo específico."""
    sql = """
        SELECT e.id, e.nombre, l.numero AS lazo, e.numero,
               te.abreviatura AS abreviatura,
               te.tipo AS tipo_categoria,
               z.numero AS zona,
               z.nombre AS zona_nombre,
               c.nodo AS central_numero,
               c.nombre AS central_nombre
        FROM ci_revision_elementos e
        JOIN ci_revision_centrales c ON c.id = e.id_central
        LEFT JOIN ci_revision_lazos l ON l.id = e.lazo
        LEFT JOIN ci_revision_zonas z ON z.id = e.id_zona
        LEFT JOIN ci_revision_tipo_elementos te ON te.id = e.tipo
        WHERE c.revision_id = %s 
        AND l.numero = %s
        ORDER BY e.numero ASC
        LIMIT %s
    """
    
    conn = conectar_mysql()
    try:
        with conn.cursor() as cur:
            cur.execute(sql, (revision_id, lazo_numero, limite))
            rows = cur.fetchall()
            return rows or []
    finally:
        conn.close()


def _formatear_fecha_actual() -> str:
    # Formato similar al ID3000 del parser: "mie 27-ago-25 13:11:42"
    now = datetime.datetime.now()
    dias = ["lun", "mar", "mie", "jue", "vie", "sab", "dom"]
    meses = ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"]
    dia_semana = dias[now.weekday()]
    mes_txt = meses[now.month - 1]
    return f"{dia_semana} {now.day:02d}-{mes_txt}-{now.year % 100:02d} {now.hour:02d}:{now.minute:02d}:{now.second:02d}"


def _tipo_dispositivo(abreviatura: str) -> str:
    return ABREVIATURA_TO_TIPO.get((abreviatura or "").upper(), (abreviatura or "OPTI").upper())


def _prefijo_dispositivo(tipo_categoria: Any) -> str:
    # 1 = sensor, otro = módulo
    try:
        return "Sens." if int(tipo_categoria) == 1 else "Mod."
    except Exception:
        return "Sens."


def _crear_evento(elemento: Dict[str, Any], tipo_evento: str) -> str:
    evento_id = str(random.randint(10000, 99999))
    fecha = _formatear_fecha_actual()
    tipo_disp = _tipo_dispositivo(elemento.get("abreviatura", "OPTI"))
    prefijo = _prefijo_dispositivo(elemento.get("tipo_categoria"))
    lazo = elemento.get("lazo") or 1
    numero = elemento.get("numero") or 1
    zona = elemento.get("zona") or 1
    zona_nombre = elemento.get("zona_nombre") or "Planta Baja"
    nombre = elemento.get("nombre") or "elemento"

    if tipo_evento == "ALARMA":
        valor = random.randint(90, 180)
        evento = f"""{evento_id}: {fecha}: ** ALARMA ** L{lazo} {prefijo} {numero} - Zona {zona} - {tipo_disp}
Zona: {zona_nombre}
Des: {nombre}
Valor: {valor}%"""
    elif tipo_evento == "PRUEBA":
        valor = random.randint(95, 120)
        evento = f"""{evento_id}: {fecha}: (PRUEBA) DE ALARMA L{lazo} {prefijo} {numero} - Zona {zona} - {tipo_disp}
Zona: {zona_nombre}
Des: {nombre}
Valor: {valor}%"""
    elif tipo_evento == "AVERIA":
        if random.choice([True, False]):
            evento = f"""{evento_id}: {fecha}: AVERIA EN DATOS DEL ELEMENTO L{lazo} {prefijo} {numero} - Zona {zona} - {tipo_disp}
Zona: {zona_nombre}
Des: {nombre}"""
        else:
            central_num = elemento.get("central_numero") or 1
            central_nom = elemento.get("central_nombre") or "recepcion"
            evento = f"""{evento_id}: {fecha}: 
CENTRAL {central_num} {central_nom} :AVERIA TRANSMISION"""
    else:  # RESTABLECIMIENTO
        evento = f"""{evento_id}: {fecha}: REESTABLECIMIENTO DESPUES DE ALARMA L{lazo} {prefijo} {numero} - Zona {zona} - {tipo_disp}
Zona: {zona_nombre}
Des: {nombre}"""

    return evento


def enviar_evento(url_base: str, evento_texto: str, revision_id: Optional[int] = None) -> bool:
    payload: Dict[str, Any] = {"evento": evento_texto}
    if revision_id:
        payload["revision_id"] = revision_id
    try:
        resp = requests.post(
            url_base.rstrip("/") + DEFAULT_ENDPOINT_EVENTO,
            json=payload,
            timeout=DEFAULT_TIMEOUT,
            headers={"Content-Type": "application/json"},
        )
        if resp.status_code == 200:
            try:
                j = resp.json()
            except Exception:
                print("❌ Respuesta no JSON del servidor")
                return False
            if j.get("success"):
                print(f"✅ Enviado OK (evento_id: {j.get('evento_id', 'N/A')})")
                return True
            print(f"❌ Error lógico del servidor: {j}")
            return False
        print(f"❌ HTTP {resp.status_code}: {resp.text[:200]}")
        return False
    except requests.exceptions.RequestException as e:
        print(f"❌ Error de red al enviar evento: {e}")
        return False


def ejecutar(revision_id: int, url_base: str, intervalo: int, limite: int, modo: str, jitter: float) -> None:
    # Limitar a 20 elementos máximo por lazo
    limite_elementos = min(limite, 20) if limite > 0 else 20
    
    # Obtener todos los lazos ordenados
    lazos = obtener_lazos_ordenados(revision_id)
    if not lazos:
        print("❌ No se encontraron lazos con detectores en la revisión")
        print("💡 Verifica credenciales y que existan centrales y detectores para esa revisión")
        return

    print(f"✅ {len(lazos)} lazos encontrados: {lazos}")
    print(f"🎯 Procesando {limite_elementos} elementos por lazo en orden secuencial")

    # Solo eventos de PRUEBA
    tipo_evento = "PRUEBA"

    idx_lazo = 0
    while True:
        if idx_lazo >= len(lazos):
            if modo == "una_vez":
                print("✅ Completado procesamiento de todos los lazos")
                break
            else:
                print("🔄 Reiniciando desde el primer lazo...")
                idx_lazo = 0
        
        lazo_actual = lazos[idx_lazo]
        print(f"\n🎯 Procesando Lazo {lazo_actual}")
        
        # Obtener elementos del lazo actual (sensores y módulos)
        elementos = obtener_detectores_lazo(revision_id, lazo_actual, limite_elementos)
        if not elementos:
            print(f"⚠️ No se encontraron elementos en el lazo {lazo_actual}")
            idx_lazo += 1
            continue
        
        print(f"📋 {len(elementos)} elementos encontrados en Lazo {lazo_actual}")
        
        # Procesar elementos del lazo actual
        for idx, elemento in enumerate(elementos):
            print(f"📡 Generando {tipo_evento} para L{elemento.get('lazo')} N{elemento.get('numero')} - {elemento.get('nombre')}")
            evento = _crear_evento(elemento, tipo_evento)
            enviar_evento(url_base, evento, revision_id)
            
            # Esperar entre eventos (excepto en el último del lazo)
            if idx < len(elementos) - 1:
                delay = max(0.0, float(intervalo) + (random.uniform(0.0, float(jitter)) if jitter and float(jitter) > 0 else 0.0))
                print(f"⏳ Esperando {delay:.1f} s...")
                time.sleep(delay)
        
        # Pasar al siguiente lazo
        idx_lazo += 1
        
        # Si es modo una_vez y hemos procesado todos los lazos, salir
        if modo == "una_vez" and idx_lazo >= len(lazos):
            print("✅ Completado procesamiento de todos los lazos")
            break
        
        # Pausa entre lazos
        if idx_lazo < len(lazos):
            delay = max(0.0, float(intervalo) + (random.uniform(0.0, float(jitter)) if jitter and float(jitter) > 0 else 0.0))
            print(f"⏳ Pausa entre lazos: {delay:.1f} s...")
            time.sleep(delay)


def parse_args(argv: List[str]) -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Generador de eventos desde MySQL")
    p.add_argument("--revision", type=int, default=DEFAULT_REVISION_ID, help="ID de revisión")
    p.add_argument("--url", type=str, default=DEFAULT_SERVER_URL, help="URL base del servidor Laravel")
    p.add_argument("--intervalo", type=int, default=DEFAULT_INTERVALO, help="Segundos entre eventos")
    p.add_argument("--limite", type=int, default=DEFAULT_LIMITE, help="Número máximo de elementos a consultar")
    p.add_argument("--modo", choices=["loop", "una_vez"], default="loop", help="Enviar en bucle o una sola pasada")
    p.add_argument("--jitter", type=float, default=DEFAULT_JITTER, help="Jitter aleatorio adicional (0..jitter) en segundos")
    return p.parse_args(argv)


def main(argv: List[str]) -> int:
    args = parse_args(argv)
    print("🎯 GENERADOR MySQL → /api/central/evento")
    print("DB:", DB_HOST, DB_DATABASE, DB_USERNAME)
    try:
        ejecutar(args.revision, args.url, args.intervalo, args.limite, args.modo, args.jitter)
        return 0
    except KeyboardInterrupt:
        print("\n🛑 Cancelado por el usuario")
        return 0
    except Exception as e:
        print(f"❌ Error inesperado: {e}")
        return 1


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
