#!/usr/bin/env python3
"""
control_modulos.py

Aplicación gráfica (Tkinter) para invocar remotamente, vía SSH+sudo,
el script modulos.py en varios servidores (Iniciar / Detener / Reiniciar),
un botón para “Ver estado de los servicios” (consulta directa con systemctl),
y un botón para “Actualizar Gits” (sin sudo), llamando ahora a actualizargit.py.

Además, al iniciar la app se abre en pantalla completa.

Requisitos:
    pip install paramiko

Ajusta las rutas de modulos.py y actualizargit.py si no están exactamente en /home/stopcar/.
"""

import paramiko
import threading
import tkinter as tk
from tkinter.scrolledtext import ScrolledText

# ----------------------------------------------
#  CONFIGURACIÓN ZONA
# ----------------------------------------------

# Hosts donde reside modulos.py, actualizargit.py y las unidades (.service)
HOSTS = [
    "10.2.12.30",
    "10.2.12.31",
    "10.2.12.32"
]

USERNAME = "stopcar"
PASSWORD = "Mitesia635$!"      # Recuerda la M mayúscula, y el signo ! final.

# Ruta absoluta al script 'modulos.py' en cada servidor.
SCRIPT_PATH = "/home/stopcar/modulos.py"

# Ruta absoluta al script 'actualizargit.py' en cada servidor.
UPDATE_SCRIPT_PATH = "/home/stopcar/actualizargit.py"

# Puerto SSH (22 por defecto)
SSH_PORT = 22

# Diccionario que mapea cada host a la lista de servicios (.service) que quieres monitorear.
SERVICES_POR_HOST = {
    "10.2.12.30": [
        "7001-7010.service",
        "7011-7020.service"
    ],
    "10.2.12.31": [
        "7021-7049.service",
    ],
    "10.2.12.32": [
        "7050-7080.service",
        "7100.service"
    ]
}

# Botones que invocan a modulos.py con la opción correspondiente
#   1 → iniciar todos los servicios
#   2 → detener todos los servicios
#   3 → reiniciar todos los servicios
OPTIONS = {
    "Iniciar módulos":    "1",
    "Detener módulos":    "2",
    "Reiniciar módulos":  "3",
}

# ----------------------------------------------
#  FUNCIÓN: Ejecutar modulos.py en cada servidor
# ----------------------------------------------

def ejecutar_en_servidor(host: str, opcion: str, log_callback):
    """
    Se conecta vía SSH a `host`, lanza:
        sudo -S python3 /ruta/a/modulos.py
    e inyecta la contraseña + la opción (1, 2 o 3) + “5” (Salir) por stdin.
    Al finalizar, comprueba el código de salida y pinta mensaje en verde o rojo.

    - host: IP del servidor.
    - opcion: "1", "2" o "3".
    - log_callback(texto, tag): función para escribir en la UI; tag puede ser None, "success" o "error".
    """
    log_callback(f"[{host}] → Iniciando conexión SSH...", None)
    try:
        cliente = paramiko.SSHClient()
        cliente.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        cliente.connect(
            hostname=host,
            port=SSH_PORT,
            username=USERNAME,
            password=PASSWORD,
            timeout=10
        )

        comando = f"sudo -S python3 {SCRIPT_PATH}"
        stdin, stdout, stderr = cliente.exec_command(comando, get_pty=True)

        # 1) Enviamos la contraseña para sudo
        stdin.write(PASSWORD + "\n")
        stdin.flush()

        # 2) Enviamos la opción deseada (1, 2 o 3) + ENTER
        stdin.write(opcion + "\n")
        stdin.flush()

        # 3) Después de ejecutar la acción, modulos.py vuelve a mostrar menú → inyectamos “5\n” para salir
        stdin.write("5\n")
        stdin.flush()

        # 4) Recogemos stdout y stderr y los volcamos a la UI
        for line in stdout:
            line = line.rstrip("\n")
            if line:
                log_callback(f"[{host}] → STDOUT: {line}", None)

        for line in stderr:
            line = line.rstrip("\n")
            if line:
                log_callback(f"[{host}] → STDERR: {line}", None)

        # 5) Evaluamos el exit code
        exit_status = stdout.channel.recv_exit_status()
        if exit_status == 0:
            log_callback(f"[{host}] → Acción completada con éxito (exit code = 0).", "success")
        else:
            log_callback(f"[{host}] → Error al ejecutar la acción (exit code = {exit_status}).", "error")

        cliente.close()
        log_callback(f"[{host}] → Conexión finalizada.\n", None)

    except Exception as e:
        log_callback(f"[{host}] → EXCEPCIÓN: {e}\n", "error")


def ejecutar_para_todos_los_hosts(opcion: str, log_area: ScrolledText):
    """
    Lanza un thread que limpia el área de logs y ejecuta
    `ejecutar_en_servidor(...)` en cada host en serie.
    """
    def tarea():
        log_area.configure(state="normal")
        log_area.delete("1.0", tk.END)
        log_area.insert(tk.END, f"==== Iniciando acción [{opcion}] en todos los hosts ====\n\n")
        log_area.configure(state="disabled")

        for h in HOSTS:
            ejecutar_en_servidor(
                host=h,
                opcion=opcion,
                log_callback=lambda texto, tag=None: append_log_color(texto, log_area, tag)
            )

        append_log_color("==== Todas las acciones completadas ====\n", log_area, None)

    threading.Thread(target=tarea, daemon=True).start()


# ----------------------------------------------
#  FUNCIÓN: “Ver estado de los servicios” directo por SSH
# ----------------------------------------------

def verificar_estado_servicios(log_area: ScrolledText):
    """
    Conecta a cada HOST vía SSH y revisa cada unidad en SERVICES_POR_HOST[host]:
      1) `echo 'PASSWORD' | sudo -S systemctl is-active <servicio>`
         → devuelve “active” o “inactive”
      2) `echo 'PASSWORD' | sudo -S systemctl status <servicio> --no-pager -n 3`
         → extrae sólo las últimas 3 líneas del status

    Muestra:
      [host] → Servicio <unidad>: ACTIVO   (en VERDE)
      [host] → Servicio <unidad>: INACTIVO (en ROJO)
      [host] → Fragmento de status :
         <últimas 3 líneas de systemctl status>
    """
    def tarea_estado():
        log_area.configure(state="normal")
        log_area.delete("1.0", tk.END)
        log_area.insert(tk.END, "==== Verificando estado de servicios en todos los hosts ====\n\n")
        log_area.configure(state="disabled")

        for host in HOSTS:
            servicios = SERVICES_POR_HOST.get(host, [])
            if not servicios:
                append_log_color(f"[{host}] → No hay servicios definidos en SERVICES_POR_HOST.\n", log_area, "error")
                continue

            append_log_color(f"[{host}] → Conexión SSH para estado de servicios...\n", log_area, None)

            try:
                cliente = paramiko.SSHClient()
                cliente.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                cliente.connect(
                    hostname=host,
                    port=SSH_PORT,
                    username=USERNAME,
                    password=PASSWORD,
                    timeout=10
                )

                for servicio in servicios:
                    # 1) Revisar si está activo o inactivo
                    cmd_is_active = f"echo '{PASSWORD}' | sudo -S systemctl is-active {servicio}"
                    stdin1, stdout1, stderr1 = cliente.exec_command(cmd_is_active)
                    salida1 = stdout1.read().decode("utf-8", errors="ignore").strip()
                    _ = stderr1.read()  # descartamos stderr de esta llamada

                    if salida1 == "active":
                        append_log_color(f"[{host}] → Servicio {servicio}: ACTIVO\n", log_area, "success")
                    else:
                        append_log_color(f"[{host}] → Servicio {servicio}: INACTIVO\n", log_area, "error")

                    # 2) Obtener un pequeño fragmento de systemctl status (últimas 3 líneas)
                    cmd_status = f"echo '{PASSWORD}' | sudo -S systemctl status {servicio} --no-pager -n 3"
                    stdin2, stdout2, stderr2 = cliente.exec_command(cmd_status)
                    lines = stdout2.read().decode("utf-8", errors="ignore").splitlines()
                    _ = stderr2.read()  # descartamos stderr de status
                    if lines:
                        append_log_color(f"[{host}] → Fragmento de status de {servicio}:\n", log_area, None)
                        for l in lines[-3:]:
                            append_log_color(f"    {l}\n", log_area, None)
                    else:
                        append_log_color(f"[{host}] → No se pudo obtener status para {servicio} (o está vacío)\n", log_area, "error")
                    append_log_color("\n", log_area, None)

                cliente.close()
                append_log_color(f"[{host}] → Verificación finalizada.\n\n", log_area, None)

            except Exception as e:
                append_log_color(f"[{host}] → EXCEPCIÓN al verificar estado: {e}\n\n", log_area, "error")

        append_log_color("==== Estado de todos los servicios finalizado ====\n", log_area, None)

    threading.Thread(target=tarea_estado, daemon=True).start()


# ----------------------------------------------
#  FUNCIÓN: “Actualizar Gits” (sin sudo)
# ----------------------------------------------

def ejecutar_actualizargit(host: str, log_callback):
    """
    Se conecta vía SSH a `host`, lanza:
        python3 /home/stopcar/actualizargit.py
    (sin sudo). Luego volca stdout/stderr y colorea el mensaje final.
    """
    log_callback(f"[{host}] → Conexión SSH para actualizar gits...", None)
    try:
        cliente = paramiko.SSHClient()
        cliente.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        cliente.connect(
            hostname=host,
            port=SSH_PORT,
            username=USERNAME,
            password=PASSWORD,
            timeout=10
        )

        # Ejecutamos el script sin sudo:
        comando = f"python3 {UPDATE_SCRIPT_PATH}"
        stdin, stdout, stderr = cliente.exec_command(comando)

        # Recogemos stdout y stderr
        for line in stdout:
            line = line.rstrip("\n")
            if line:
                log_callback(f"[{host}] → STDOUT: {line}", None)

        for line in stderr:
            line = line.rstrip("\n")
            if line:
                log_callback(f"[{host}] → STDERR: {line}", None)

        # Comprobamos exit code
        exit_status = stdout.channel.recv_exit_status()
        if exit_status == 0:
            log_callback(f"[{host}] → actualizargit.py completado con éxito (exit code = 0).", "success")
        else:
            log_callback(f"[{host}] → actualizargit.py ERROR (exit code = {exit_status}).", "error")

        cliente.close()
        log_callback(f"[{host}] → Conexión finalizada.\n", None)

    except Exception as e:
        log_callback(f"[{host}] → EXCEPCIÓN al actualizar gits: {e}\n", "error")


def ejecutar_actualizar_gits(log_area: ScrolledText):
    """
    Lanza un thread que limpia el área de logs y ejecuta
    `ejecutar_actualizargit(...)` para cada host en serie.
    """
    def tarea_gits():
        log_area.configure(state="normal")
        log_area.delete("1.0", tk.END)
        log_area.insert(tk.END, "==== Actualizando Gits en todos los hosts ====\n\n")
        log_area.configure(state="disabled")

        for h in HOSTS:
            ejecutar_actualizargit(
                host=h,
                log_callback=lambda texto, tag=None: append_log_color(texto, log_area, tag)
            )

        append_log_color("==== Actualización de Gits completada ====\n", log_area, None)

    threading.Thread(target=tarea_gits, daemon=True).start()


# ----------------------------------------------
#  AUXILIAR: Insertar texto coloreado en el ScrolledText
# ----------------------------------------------

def append_log_color(texto: str, log_area: ScrolledText, tag: str):
    """
    Inserta `texto` en log_area. Si tag == "success", pinta en VERDE;
    si tag == "error", pinta en ROJO; si tag es None, color por defecto.
    Usa log_area.after(...) para asegurar que se ejecute en el hilo principal de Tkinter.
    """
    if not texto.endswith("\n"):
        texto += "\n"

    def inner():
        log_area.configure(state="normal")
        if tag is None:
            log_area.insert(tk.END, texto)
        else:
            log_area.insert(tk.END, texto, tag)
        log_area.see(tk.END)
        log_area.configure(state="disabled")

    log_area.after(0, inner)


# ----------------------------------------------
#  INTERFAZ GRÁFICA (Tkinter)
# ----------------------------------------------

def crear_ui():
    root = tk.Tk()
    root.title("Panel de Control de Módulos Remotos")

    # Abrir en pantalla completa (fullscreen)
    root.attributes("-fullscreen", True)
    # Permitir salir de fullscreen con Esc
    root.bind("<Escape>", lambda e: root.attributes("-fullscreen", False))

    # Frame para botones en la parte superior
    frame_botones = tk.Frame(root)
    frame_botones.pack(fill="x", padx=10, pady=10)

    # Botones principales: Iniciar / Detener / Reiniciar
    for texto, opcion in OPTIONS.items():
        btn = tk.Button(
            frame_botones,
            text=texto,
            width=20,
            padx=5,
            pady=5,
            command=lambda op=opcion: ejecutar_para_todos_los_hosts(op, log_area)
        )
        btn.pack(side="left", padx=5)

    # Botón extra: "Ver estado de los servicios"
    btn_ver_estado = tk.Button(
        frame_botones,
        text="Ver estado de los servicios",
        width=25,
        padx=5,
        pady=5,
        command=lambda: verificar_estado_servicios(log_area)
    )
    btn_ver_estado.pack(side="left", padx=5)

    # Botón extra: "Actualizar Gits"
    btn_update_git = tk.Button(
        frame_botones,
        text="Actualizar Gits",
        width=20,
        padx=5,
        pady=5,
        command=lambda: ejecutar_actualizar_gits(log_area)
    )
    btn_update_git.pack(side="left", padx=5)

    # Botón "Salir"
    btn_salir = tk.Button(
        frame_botones,
        text="Salir",
        width=12,
        padx=5,
        pady=5,
        command=lambda: root.quit()
    )
    btn_salir.pack(side="right", padx=5)

    # Área de texto con scroll para mostrar logs/resultados
    log_area = ScrolledText(root, state="disabled", wrap="word", height=32)
    log_area.pack(fill="both", padx=10, pady=(0,10), expand=True)

    # Definir etiquetas de color en el Text widget:
    log_area.tag_configure("success", foreground="green")
    log_area.tag_configure("error",   foreground="red")

    # Mensaje inicial
    log_area.configure(state="normal")
    log_area.insert(tk.END, "» Bienvenido al Panel de Control de Módulos Remotos\n\n")
    log_area.configure(state="disabled")

    root.mainloop()


if __name__ == "__main__":
    crear_ui()
