import customtkinter as ctk
import subprocess
import threading
import queue
import os
import signal

class ProcessRunner:
    """
    Una clase para ejecutar procesos externos en un hilo separado y mostrar
    su salida en tiempo real en un widget de texto de customtkinter.
    """
    def __init__(self, command, output_widget, use_tor=False, cwd=None, status_label=None, progress_bar=None, requires_sudo=False):
        """
        Args:
            command (list): El comando a ejecutar como una lista de strings.
            output_widget (ctk.CTkTextbox): El widget donde se mostrará la salida.
            use_tor (bool): Si es True, antepone 'torsocks' al comando.
            cwd (str, optional): Directorio de trabajo actual para el subproceso.
                                 Si es None, se usa el directorio de trabajo del padre.
            status_label (ctk.CTkLabel, optional): Etiqueta para mostrar el estado del proceso.
            progress_bar (ctk.CTkProgressBar, optional): Barra de progreso para indicar actividad.
            requires_sudo (bool): Si es True, solicitará la contraseña de sudo antes de ejecutar el comando.
        """
        self.original_command = command # Store original command
        self.command = list(command) # Create a mutable copy for modification
        if use_tor:
            self.command.insert(0, "torsocks")
            
        self.output_widget = output_widget
        self.process = None
        self.output_queue = queue.Queue()
        self.running = False
        self.cwd = cwd
        self.status_label = status_label
        self.progress_bar = progress_bar
        self.requires_sudo = requires_sudo

        # Define standard colors for status feedback
        self.running_color = "orange"
        self.success_color = "green"
        self.error_color = "red"
        self.stopped_color = "red"

    def start(self):
        """Inicia la ejecución del proceso en un hilo separado."""
        if self.running:
            return

        self.running = True
        self.output_widget.configure(state="normal")
        self.output_widget.delete("1.0", "end")
        self.output_widget.configure(state="disabled")

        # Handle sudo password prompt on the main thread before starting the background process
        if self.requires_sudo:
            # Using ctk.CTkInputDialog needs to be on the main GUI thread
            self.output_widget.after(0, self._prompt_for_sudo_password)
        else:
            self._start_process_thread()

    def _prompt_for_sudo_password(self):
        """Muestra un diálogo para solicitar la contraseña de sudo."""
        dialog = ctk.CTkInputDialog(text="El comando requiere privilegios de sudo.\nPor favor, ingresa tu contraseña:", title="Contraseña de Sudo")
        password = dialog.get_input()

        if password:
            self.sudo_password = password
            self._start_process_thread()
        else:
            self.running = False
            self.output_queue.put("\n[ERROR]: Contraseña de sudo no proporcionada o diálogo cancelado.\n")
            self.output_widget.configure(state="normal")
            self.output_widget.insert("end", "\n[ERROR]: Contraseña de sudo no proporcionada o diálogo cancelado.\n")
            self.output_widget.configure(state="disabled")
            if self.status_label:
                self.status_label.configure(text="Error: Sudo cancelado", text_color="red")
            if self.progress_bar:
                self.progress_bar.stop()

    def _start_process_thread(self):
        """Inicia el hilo para ejecutar el proceso externo."""
        self.thread = threading.Thread(target=self._run_process)
        self.thread.daemon = True
        self.thread.start()

        # Iniciar el chequeo periódico de la cola de salida
        self.output_widget.after(100, self._check_queue)

    def _run_process(self):
        """El método que se ejecuta en el hilo y maneja el subproceso."""
        process_command = list(self.command) # Use a local copy for modification

        stdin_pipe = None
        if self.requires_sudo and hasattr(self, 'sudo_password'):
            process_command.insert(0, "-S")
            process_command.insert(0, "sudo")
            stdin_pipe = subprocess.PIPE

        try:
            # Usar os.setsid para crear una nueva sesión de proceso.
            # Esto permite matar el proceso y todos sus hijos.
            self.process = subprocess.Popen(
                process_command,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                stdin=stdin_pipe, # Set stdin if password is needed
                text=True,
                encoding='utf-8',
                errors='replace',
                bufsize=1,
                preexec_fn=os.setsid,
                cwd=self.cwd # Pasa el directorio de trabajo
            )

            if self.requires_sudo and hasattr(self, 'sudo_password'):
                self.process.stdin.write(self.sudo_password + '\n')
                self.process.stdin.flush()
                del self.sudo_password # Clear password from memory after use

            # Leer la salida línea por línea
            for line in iter(self.process.stdout.readline, ''):
                self.output_queue.put(line)
                if not self.running:
                    break
            
            self.process.stdout.close()
            return_code = self.process.wait()

            if return_code != 0:
                error_msg = f"El comando '{self.original_command[0].split(os.sep)[-1]}' salió con código de error {return_code}."
                # Intentar leer los últimos mensajes de error si no se detuvo por el usuario
                if self.running: # Check if the process was not stopped by user
                    # Ensure final_output is retrieved correctly if the widget is disabled
                    self.output_widget.after(0, lambda: self._get_and_process_final_output_on_main_thread(error_msg))
                    return # Exit early after scheduling UI update

                else: # Process was stopped by user
                    self.output_queue.put(f"\n[ERROR]: {error_msg}\n")
                    if self.status_label:
                        first_line_error = error_msg.split('\n')[0]
                        self.status_label.configure(text=f"Error: {first_line_error}", text_color="red")
                    if self.progress_bar:
                        self.progress_bar.stop()
                    self.running = False # Mark as not running due to error
                    return # Exit early to prevent "Completado" status

            else: # Command completed successfully
                if self.running:
                    self.output_queue.put(None) # Signal end of process
                    if self.status_label and "Error" not in self.status_label.cget("text"):
                        self.output_widget.after(0, lambda: self.status_label.configure(text="Completado.", text_color="green"))
                    if self.progress_bar and self.progress_bar._running:
                        self.output_widget.after(0, lambda: self.progress_bar.stop())
                self.running = False # Mark as not running after successful completion

        except FileNotFoundError:
            error_msg = f"Comando '{self.original_command[0].split(os.sep)[-1]}' no encontrado."
            self.output_queue.put(f"[ERROR]: {error_msg}\n")
            self.output_queue.put("Asegúrate de que está instalado y en el PATH del sistema.\n")
            if self.status_label:
                self.status_label.configure(text=f"Error: {error_msg}", text_color="red")
            if self.progress_bar:
                self.progress_bar.stop()
            self.running = False # Ensure running is set to False on error
        except Exception as e:
            error_msg = f"Ocurrió un error inesperado al ejecutar el comando: {e}"
            self.output_queue.put(f"[ERROR]: {error_msg}\n")
            if self.status_label:
                self.status_label.configure(text=f"Error: {error_msg.split(':')[0]}", text_color="red")
            if self.progress_bar:
                self.progress_bar.stop()
            self.running = False # Ensure running is set to False on error
        finally:
            if self.running: # If for some reason it's still running, ensure it's stopped
                self.running = False
                # If process ended unexpectedly without an error code, ensure signal for completion is sent
                self.output_queue.put(None) 
                if self.progress_bar and self.progress_bar._running:
                    self.output_widget.after(0, lambda: self.progress_bar.stop())
                if self.status_label and "Error" not in self.status_label.cget("text"):
                    self.output_widget.after(0, lambda: self.status_label.configure(text="Completado.", text_color="green"))
    
    def _get_and_process_final_output_on_main_thread(self, error_msg):
        """Helper to get final output and process error on main thread."""
        final_output = self.output_widget.get("1.0", "end-1c")
        if final_output and "[PROCESO DETENIDO POR EL USUARIO]" not in final_output:
            error_msg += "\nRevisa la salida anterior para más detalles."
        self.output_queue.put(f"\n[ERROR]: {error_msg}\n")
        if self.status_label:
            first_line_error = error_msg.split('\n')[0]
            self.status_label.configure(text=f"Error: {first_line_error}", text_color="red")
        if self.progress_bar:
            self.progress_bar.stop()
        self.running = False


    def _check_queue(self):
        """
        Comprueba la cola de salida y actualiza el widget de texto.
        Se ejecuta en el hilo principal de la GUI.
        """
        while not self.output_queue.empty():
            line = self.output_queue.get_nowait()
            if line is None: # Señal de fin de proceso
                return
            
            self.output_widget.configure(state="normal")
            self.output_widget.insert("end", line)
            self.output_widget.see("end") # Auto-scroll
            self.output_widget.configure(state="disabled")

        if self.running:
            self.output_widget.after(100, self._check_queue)

    def stop(self):
        """Detiene el proceso en ejecución."""
        if self.process and self.running:
            self.running = False
            try:
                # Matar todo el grupo de procesos
                os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
                self.output_queue.put("\n\n[PROCESO DETENIDO POR EL USUARIO]\n")
                if self.status_label:
                    self.status_label.configure(text="Detenido.", text_color="red")
                if self.progress_bar:
                    self.progress_bar.stop()
            except ProcessLookupError:
                # El proceso ya podría haber terminado
                pass
            except Exception as e:
                self.output_queue.put(f"\n[ERROR]: Error al detener el proceso: {e}\n")
                if self.status_label:
                    self.status_label.configure(text="Error al detener.", text_color="red")
                if self.progress_bar:
                    self.progress_bar.stop()
