import os
import json
import subprocess
import sys
import threading
import time
from typing import Dict, Any, List, Optional
from datetime import datetime
import uuid
import glob

class CSharpLauncherManager:
    def __init__(self, data_path: str):
        self.data_path = data_path
        self.launcher_config_path = os.path.join(data_path, "csharp_launcher_projects.json")
        self.favorites_path = os.path.join(data_path, "csharp_launcher_favorites.json")
        self.script_metadata_path = os.path.join(data_path, "csharp_launcher_metadata.json")
        
        # Procesos en ejecución para C#
        self.running_processes = {}
        self.process_lock = threading.Lock()
        
        # Inicializar archivos si no existen
        self._initialize_files()
    
    def _initialize_files(self):
        """Crear archivos de configuración por defecto si no existen"""
        # Inicializar csharp_launcher_projects.json
        if not os.path.exists(self.launcher_config_path):
            default_config = {
                "version": "1.0",
                "projects": [],
                "categories": {
                    "Aplicaciones": {
                        "color": "#3B82F6",
                        "icon": "🖥️",
                        "subcategories": ["Desktop", "Consola", "WPF"]
                    },
                    "Herramientas": {
                        "color": "#10B981", 
                        "icon": "🔧",
                        "subcategories": ["Utilidades", "Automatización", "Sistema"]
                    },
                    "Análisis": {
                        "color": "#8B5CF6",
                        "icon": "📊",
                        "subcategories": ["Datos", "Reportes", "Business Intelligence"]
                    },
                    "Desarrollo": {
                        "color": "#F59E0B",
                        "icon": "💻",
                        "subcategories": ["Testing", "Build Tools", "DevOps"]
                    },
                    "APIs": {
                        "color": "#EF4444",
                        "icon": "🌐",
                        "subcategories": ["REST", "GraphQL", "Microservicios"]
                    },
                    "Otros": {
                        "color": "#6B7280",
                        "icon": "📁",
                        "subcategories": ["Misceláneos"]
                    }
                },
                "settings": {
                    "default_execution_directory": "project_directory",
                    "search_debug_first": True,
                    "show_build_output": True
                }
            }
            with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
                json.dump(default_config, f, indent=2, ensure_ascii=False)
        
        # Inicializar csharp_launcher_favorites.json
        if not os.path.exists(self.favorites_path):
            default_favorites = {"favorites": []}
            with open(self.favorites_path, 'w', encoding='utf-8') as f:
                json.dump(default_favorites, f, indent=2, ensure_ascii=False)

        # Inicializar csharp_launcher_metadata.json
        if not os.path.exists(self.script_metadata_path):
            default_metadata = {
                "version": "1.0",
                "executable_metadata": {}
            }
            with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
                json.dump(default_metadata, f, indent=2, ensure_ascii=False)

    def get_csharp_projects(self) -> List[Dict[str, Any]]:
        """Obtener todos los proyectos C#"""
        try:
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            return config.get("projects", [])
        except Exception as e:
            print(f"Error loading C# projects: {e}")
            return []

    def get_csharp_project(self, project_id: str) -> Optional[Dict[str, Any]]:
        """Obtener un proyecto específico por ID"""
        projects = self.get_csharp_projects()
        for project in projects:
            if project.get("id") == project_id:
                return project
        return None

    def add_csharp_project(self, project_data: Dict[str, Any]) -> Dict[str, str]:
        """Agregar nuevo proyecto C#"""
        try:
            # Validar datos requeridos
            required_fields = ["name", "directory"]
            for field in required_fields:
                if not project_data.get(field):
                    return {"status": "error", "message": f"Campo requerido: {field}"}
            
            # Validar que el directorio existe
            if not os.path.isdir(project_data["directory"]):
                return {"status": "error", "message": "El directorio especificado no existe"}
            
            # Generar ID único si no se proporciona
            if not project_data.get("id"):
                project_data["id"] = str(uuid.uuid4())[:8]
            
            # Verificar que el ID no exista
            if self.get_csharp_project(project_data["id"]):
                return {"status": "error", "message": "Ya existe un proyecto con este ID"}
            
            # Agregar campos por defecto
            current_time = datetime.now().isoformat() + "Z"
            project_data.setdefault("description", "")
            project_data.setdefault("category", "Otros")
            project_data.setdefault("version", "1.0")
            project_data.setdefault("author", "")
            project_data.setdefault("tags", [])
            project_data.setdefault("dotnet_version", "")  # Versión de .NET
            project_data.setdefault("created_date", current_time)
            project_data["updated_date"] = current_time
            
            # Cargar configuración y agregar proyecto
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            
            config["projects"].append(project_data)
            
            with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            
            return {"status": "success", "message": "Proyecto agregado exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error agregando proyecto: {str(e)}"}

    def update_csharp_project(self, project_id: str, project_data: Dict[str, Any]) -> Dict[str, str]:
        """Actualizar proyecto existente"""
        try:
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            
            # Buscar y actualizar el proyecto
            project_found = False
            for i, project in enumerate(config["projects"]):
                if project["id"] == project_id:
                    # Mantener ID y fechas de creación
                    project_data["id"] = project_id
                    project_data["created_date"] = project.get("created_date", datetime.now().isoformat() + "Z")
                    project_data["updated_date"] = datetime.now().isoformat() + "Z"
                    
                    config["projects"][i] = project_data
                    project_found = True
                    break
            
            if not project_found:
                return {"status": "error", "message": "Proyecto no encontrado"}
            
            with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            
            return {"status": "success", "message": "Proyecto actualizado exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error actualizando proyecto: {str(e)}"}

    def delete_csharp_project(self, project_id: str) -> Dict[str, str]:
        """Eliminar proyecto C#"""
        try:
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            
            # Filtrar el proyecto a eliminar
            original_count = len(config["projects"])
            config["projects"] = [p for p in config["projects"] if p["id"] != project_id]
            
            if len(config["projects"]) == original_count:
                return {"status": "error", "message": "Proyecto no encontrado"}
            
            with open(self.launcher_config_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            
            # Limpiar metadatos y favoritos relacionados
            self._cleanup_metadata_for_project(project_id)
            self._cleanup_favorites_for_project(project_id)
            self._cleanup_favorites_for_project(project_id)
            
            return {"status": "success", "message": "Proyecto eliminado exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error eliminando proyecto: {str(e)}"}

    def execute_csharp_executable(self, project_id: str, exe_name: str, exe_args: List[str], 
                                 broadcast_func, working_dir: str = None) -> Dict[str, Any]:
        """Ejecutar un ejecutable C#"""
        try:
            project = self.get_csharp_project(project_id)
            if not project:
                return {"status": "error", "message": "Proyecto no encontrado"}
            
            # Buscar el ejecutable
            executables = self.get_all_project_executables(project_id)
            exe_info = None
            for exe in executables:
                if exe["filename"] == exe_name:
                    exe_info = exe
                    break
            
            if not exe_info:
                return {"status": "error", "message": f"Ejecutable '{exe_name}' no encontrado"}
            
            exe_path = exe_info["full_path"]
            
            # Determinar directorio de trabajo
            if working_dir and os.path.isdir(working_dir):
                work_dir = working_dir
            else:
                work_dir = os.path.dirname(exe_path)
            
            # Preparar comando
            cmd = [exe_path] + exe_args
            
            execution_id = str(uuid.uuid4())[:8]
            
            broadcast_func(f"🚀 Ejecutando: {exe_info['display_name']}")
            broadcast_func(f"📁 Directorio: {work_dir}")
            broadcast_func(f"⚡ Comando: {' '.join(cmd)}")
            broadcast_func("=" * 50)
            
            try:
                # Ejecutar el proceso
                process = subprocess.Popen(
                    cmd,
                    cwd=work_dir,
                    creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
                )
                
                # Almacenar información del proceso
                with self.process_lock:
                    self.running_processes[process.pid] = {
                        "project_id": project_id,
                        "exe_name": exe_name,
                        "display_name": exe_info['display_name'],
                        "start_time": datetime.now(),
                        "process": process
                    }
                
                broadcast_func(f"✅ Proceso iniciado con PID: {process.pid}")
                
                return {
                    "status": "success",
                    "message": f"Ejecutable '{exe_info['display_name']}' iniciado",
                    "pid": process.pid,
                    "execution_id": execution_id
                }
                
            except subprocess.SubprocessError as e:
                return {"status": "error", "message": f"Error ejecutando el proceso: {str(e)}"}
            except Exception as e:
                return {"status": "error", "message": f"Error inesperado: {str(e)}"}
            
        except Exception as e:
            return {"status": "error", "message": f"Error ejecutando ejecutable: {str(e)}"}

    def get_favorites(self) -> List[Dict[str, Any]]:
        """Obtener lista de favoritos"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            return data.get("favorites", [])
        except Exception as e:
            print(f"Error loading C# favorites: {e}")
            return []

    def toggle_favorite(self, project_id: str, exe_name: str) -> Dict[str, str]:
        """Agregar o quitar de favoritos"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            favorites = data.get("favorites", [])
            favorite_key = f"{project_id}_{exe_name}"
            
            # Buscar si ya existe
            existing_favorite = None
            for fav in favorites:
                if fav.get("project_id") == project_id and fav.get("exe_name") == exe_name:
                    existing_favorite = fav
                    break
            
            if existing_favorite:
                # Quitar de favoritos
                favorites.remove(existing_favorite)
                message = "Removido de favoritos"
                is_favorite = False
            else:
                # Agregar a favoritos
                favorites.append({
                    "id": favorite_key,
                    "project_id": project_id,
                    "exe_name": exe_name,
                    "added_date": datetime.now().isoformat() + "Z"
                })
                message = "Agregado a favoritos"
                is_favorite = True
            
            data["favorites"] = favorites
            
            with open(self.favorites_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            
            return {"status": "success", "message": message, "is_favorite": is_favorite}
        
        except Exception as e:
            return {"status": "error", "message": f"Error toggle favorite: {str(e)}"}

    def get_categories(self) -> Dict[str, Any]:
        """Obtener categorías disponibles"""
        try:
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            return config.get("categories", {})
        except Exception as e:
            print(f"Error loading C# categories: {e}")
            return {}

    def get_running_processes(self) -> List[Dict[str, Any]]:
        """Obtener procesos C# en ejecución"""
        processes = []
        
        with self.process_lock:
            for pid, info in list(self.running_processes.items()):
                try:
                    # Verificar si el proceso sigue activo
                    process = info["process"]
                    if process.poll() is None:
                        processes.append({
                            "pid": pid,
                            "project_id": info["project_id"],
                            "exe_name": info["exe_name"],
                            "display_name": info["display_name"],
                            "start_time": info["start_time"].isoformat() + "Z"
                        })
                    else:
                        # Proceso terminado, remover de la lista
                        del self.running_processes[pid]
                except:
                    # Error verificando proceso, remover
                    del self.running_processes[pid]
            
            return processes

    def terminate_process(self, pid: int) -> Dict[str, str]:
        """Cerrar un proceso C#"""
        try:
            with self.process_lock:
                if pid in self.running_processes:
                    process_info = self.running_processes[pid]
                    process = process_info["process"]
                    
                    try:
                        process.terminate()
                        process.wait(timeout=5)
                        del self.running_processes[pid]
                        return {
                            "status": "success",
                            "message": f"Proceso {process_info['display_name']} (PID: {pid}) terminado"
                        }
                    except subprocess.TimeoutExpired:
                        process.kill()
                        del self.running_processes[pid]
                        return {
                            "status": "success",
                            "message": f"Proceso {process_info['display_name']} (PID: {pid}) forzado a cerrar"
                        }
                else:
                    return {"status": "error", "message": "Proceso no encontrado en ejecución"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error terminando proceso: {str(e)}"}

    def _load_executable_metadata(self) -> Dict[str, Any]:
        """Cargar metadatos de ejecutables desde archivo"""
        try:
            with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {"version": "1.0", "executable_metadata": {}}

    def get_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
        """Obtener ejecutables de un proyecto específico (solo visibles)"""
        project = self.get_csharp_project(project_id)
        if not project:
            return []
        
        project_dir = project["directory"]
        if not os.path.isdir(project_dir):
            return []

        executables = []
        metadata = self._load_executable_metadata()
        
        # Buscar en bin/Release y bin/Debug
        search_patterns = [
            os.path.join(project_dir, "**/bin/Release/**/*.exe"),
            os.path.join(project_dir, "**/bin/Debug/**/*.exe")
        ]
        
        found_exe_files = set()
        for pattern in search_patterns:
            for exe_path in glob.glob(pattern, recursive=True):
                if os.path.isfile(exe_path):
                    found_exe_files.add(exe_path)
        
        for exe_path in found_exe_files:
            exe_name = os.path.basename(exe_path)
            exe_key = f"{project_id}_{exe_name}"
            
            # Obtener metadatos o crear por defecto
            exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
            
            # Solo incluir si no está oculto
            if not exe_metadata.get("hidden", False):
                # Determinar si es Debug o Release
                build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
                
                executables.append({
                    "filename": exe_name,
                    "full_path": exe_path,
                    "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
                    "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
                    "long_description": exe_metadata.get("long_description", ""),
                    "build_type": build_type,
                    "relative_path": os.path.relpath(exe_path, project_dir)
                })
        
        # Ordenar por nombre de display
        executables.sort(key=lambda x: x['display_name'])
        return executables

    def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
        """Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión"""
        project = self.get_csharp_project(project_id)
        if not project:
            return []
        
        project_dir = project["directory"]
        if not os.path.isdir(project_dir):
            return []

        executables = []
        metadata = self._load_executable_metadata()
        
        # Buscar en bin/Release y bin/Debug
        search_patterns = [
            os.path.join(project_dir, "**/bin/Release/**/*.exe"),
            os.path.join(project_dir, "**/bin/Debug/**/*.exe")
        ]
        
        found_exe_files = set()
        for pattern in search_patterns:
            for exe_path in glob.glob(pattern, recursive=True):
                if os.path.isfile(exe_path):
                    found_exe_files.add(exe_path)
        
        for exe_path in found_exe_files:
            exe_name = os.path.basename(exe_path)
            exe_key = f"{project_id}_{exe_name}"
            
            # Obtener metadatos o crear por defecto
            exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
            
            # Determinar si es Debug o Release
            build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
            
            executables.append({
                "filename": exe_name,
                "full_path": exe_path,
                "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
                "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
                "long_description": exe_metadata.get("long_description", ""),
                "build_type": build_type,
                "relative_path": os.path.relpath(exe_path, project_dir),
                "hidden": exe_metadata.get("hidden", False)
            })
        
        # Ordenar por nombre de display
        executables.sort(key=lambda x: x['display_name'])
        return executables

    def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]:
        """Obtener metadatos de un ejecutable específico"""
        metadata = self._load_executable_metadata()
        exe_key = f"{project_id}_{exe_name}"
        return metadata.get("executable_metadata", {}).get(exe_key, {
            "display_name": exe_name.replace('.exe', ''),
            "short_description": "Aplicación C#",
            "long_description": "",
            "hidden": False
        })

    def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]:
        """Actualizar metadatos de un ejecutable específico"""
        try:
            metadata = self._load_executable_metadata()
            exe_key = f"{project_id}_{exe_name}"
            
            if "executable_metadata" not in metadata:
                metadata["executable_metadata"] = {}
            
            if exe_key not in metadata["executable_metadata"]:
                metadata["executable_metadata"][exe_key] = {}
            
            # Actualizar campos
            metadata["executable_metadata"][exe_key].update(metadata_update)
            
            self._save_executable_metadata(metadata)
            return {"status": "success", "message": "Metadatos actualizados exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}

    def execute_csharp_executable(self, project_id: str, exe_name: str, exe_args: List[str], 
                                 broadcast_func, working_dir: str = None) -> Dict[str, Any]:
        """Ejecutar un ejecutable C#"""
        try:
            project = self.get_csharp_project(project_id)
            if not project:
                return {"status": "error", "message": "Proyecto no encontrado"}
            
            # Buscar el ejecutable
            executables = self.get_all_project_executables(project_id)
            exe_info = None
            for exe in executables:
                if exe["filename"] == exe_name:
                    exe_info = exe
                    break
            
            if not exe_info:
                return {"status": "error", "message": f"Ejecutable '{exe_name}' no encontrado"}
            
            exe_path = exe_info["full_path"]
            
            # Determinar directorio de trabajo
            if working_dir and os.path.isdir(working_dir):
                work_dir = working_dir
            else:
                work_dir = os.path.dirname(exe_path)
            
            # Preparar comando
            cmd = [exe_path] + exe_args
            
            execution_id = str(uuid.uuid4())[:8]
            
            broadcast_func(f"🚀 Ejecutando: {exe_info['display_name']}")
            broadcast_func(f"📁 Directorio: {work_dir}")
            broadcast_func(f"⚡ Comando: {' '.join(cmd)}")
            broadcast_func("=" * 50)
            
            try:
                # Ejecutar el proceso
                process = subprocess.Popen(
                    cmd,
                    cwd=work_dir,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    bufsize=1,
                    universal_newlines=True,
                    creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
                )
                
                # Almacenar información del proceso
                with self.process_lock:
                    self.running_processes[process.pid] = {
                        "project_id": project_id,
                        "exe_name": exe_name,
                        "display_name": exe_info['display_name'],
                        "start_time": datetime.now(),
                        "process": process
                    }
                
                broadcast_func(f"✅ Proceso iniciado con PID: {process.pid}")
                
                # Monitorear salida en hilo separado
                def read_output():
                    try:
                        for line in iter(process.stdout.readline, ''):
                            if line.strip():
                                broadcast_func(line.rstrip())
                    except Exception as e:
                        broadcast_func(f"Error leyendo salida: {e}")
                    finally:
                        if process.stdout:
                            process.stdout.close()
                
                output_thread = threading.Thread(target=read_output, daemon=True)
                output_thread.start()
                
                # Monitorear finalización en hilo separado
                def monitor_completion():
                    try:
                        start_time = time.time()
                        return_code = process.wait()
                        execution_time = time.time() - start_time
                        
                        # Limpiar de procesos en ejecución
                        with self.process_lock:
                            if process.pid in self.running_processes:
                                del self.running_processes[process.pid]
                        
                        if return_code == 0:
                            broadcast_func(f"✅ Proceso completado exitosamente (PID: {process.pid})")
                        else:
                            broadcast_func(f"❌ Proceso terminó con código: {return_code} (PID: {process.pid})")
                        
                        broadcast_func(f"⏱️ Tiempo de ejecución: {execution_time:.2f} segundos")
                        broadcast_func("=" * 50)
                        
                    except Exception as e:
                        broadcast_func(f"Error monitoreando proceso: {e}")
                
                monitor_thread = threading.Thread(target=monitor_completion, daemon=True)
                monitor_thread.start()
                
                return {
                    "status": "success",
                    "message": f"Ejecutable '{exe_info['display_name']}' iniciado",
                    "pid": process.pid,
                    "execution_id": execution_id
                }
                
            except subprocess.SubprocessError as e:
                return {"status": "error", "message": f"Error ejecutando el proceso: {str(e)}"}
            except Exception as e:
                return {"status": "error", "message": f"Error inesperado: {str(e)}"}
            
        except Exception as e:
            return {"status": "error", "message": f"Error ejecutando ejecutable: {str(e)}"}

    def get_favorites(self) -> List[Dict[str, Any]]:
        """Obtener lista de favoritos"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            return data.get("favorites", [])
        except Exception as e:
            print(f"Error loading C# favorites: {e}")
            return []

    def toggle_favorite(self, project_id: str, exe_name: str) -> Dict[str, str]:
        """Agregar o quitar de favoritos"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            favorites = data.get("favorites", [])
            favorite_key = f"{project_id}_{exe_name}"
            
            # Buscar si ya existe
            existing_favorite = None
            for fav in favorites:
                if fav.get("project_id") == project_id and fav.get("exe_name") == exe_name:
                    existing_favorite = fav
                    break
            
            if existing_favorite:
                # Quitar de favoritos
                favorites.remove(existing_favorite)
                message = "Removido de favoritos"
                is_favorite = False
            else:
                # Agregar a favoritos
                favorites.append({
                    "id": favorite_key,
                    "project_id": project_id,
                    "exe_name": exe_name,
                    "added_date": datetime.now().isoformat() + "Z"
                })
                message = "Agregado a favoritos"
                is_favorite = True
            
            data["favorites"] = favorites
            
            with open(self.favorites_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            
            return {
                "status": "success",
                "message": message,
                "is_favorite": is_favorite
            }
        
        except Exception as e:
            return {"status": "error", "message": f"Error gestionando favorito: {str(e)}"}

    def get_categories(self) -> Dict[str, Any]:
        """Obtener categorías disponibles"""
        try:
            with open(self.launcher_config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            return config.get("categories", {})
        except Exception as e:
            print(f"Error loading C# categories: {e}")
            return {}

    def get_running_processes(self) -> List[Dict[str, Any]]:
        """Obtener procesos C# en ejecución"""
        with self.process_lock:
            processes = []
            for pid, info in self.running_processes.items():
                try:
                    # Verificar si el proceso sigue activo
                    if info["process"].poll() is None:
                        processes.append({
                            "pid": pid,
                            "project_id": info["project_id"],
                            "exe_name": info["exe_name"],
                            "display_name": info["display_name"],
                            "start_time": info["start_time"].isoformat()
                        })
                    else:
                        # Proceso terminado, remover de la lista
                        del self.running_processes[pid]
                except:
                    # Error verificando proceso, remover
                    del self.running_processes[pid]
            
            return processes

    def terminate_process(self, pid: int) -> Dict[str, str]:
        """Cerrar un proceso C#"""
        try:
            with self.process_lock:
                if pid in self.running_processes:
                    process_info = self.running_processes[pid]
                    process = process_info["process"]
                    
                    try:
                        process.terminate()
                        process.wait(timeout=5)
                        del self.running_processes[pid]
                        return {
                            "status": "success",
                            "message": f"Proceso {process_info['display_name']} (PID: {pid}) terminado"
                        }
                    except subprocess.TimeoutExpired:
                        process.kill()
                        del self.running_processes[pid]
                        return {
                            "status": "success",
                            "message": f"Proceso {process_info['display_name']} (PID: {pid}) forzado a cerrar"
                        }
                else:
                    return {"status": "error", "message": "Proceso no encontrado en ejecución"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error terminando proceso: {str(e)}"}

    def _load_executable_metadata(self) -> Dict[str, Any]:
        """Cargar metadatos de ejecutables desde archivo"""
        try:
            with open(self.script_metadata_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {"version": "1.0", "executable_metadata": {}}

    def _save_executable_metadata(self, metadata: Dict[str, Any]):
        """Guardar metadatos de ejecutables a archivo"""
        try:
            with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"Error saving C# executable metadata: {e}")

    def _cleanup_metadata_for_project(self, project_id: str):
        """Limpiar metadatos de un proyecto eliminado"""
        try:
            metadata = self._load_executable_metadata()
            if "executable_metadata" in metadata:
                # Remover metadatos que empiecen con project_id_
                keys_to_remove = [key for key in metadata["executable_metadata"].keys() 
                                if key.startswith(f"{project_id}_")]
                for key in keys_to_remove:
                    del metadata["executable_metadata"][key]
                self._save_executable_metadata(metadata)
        except Exception as e:
            print(f"Error cleaning metadata for project {project_id}: {e}")

    def _cleanup_favorites_for_project(self, project_id: str):
        """Limpiar favoritos de un proyecto eliminado"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            # Filtrar favoritos que no sean del proyecto eliminado
            data["favorites"] = [fav for fav in data.get("favorites", []) 
                               if fav.get("project_id") != project_id]
            
            with open(self.favorites_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"Error cleaning favorites for project {project_id}: {e}")

    def get_executable_arguments(self, project_id: str, exe_name: str) -> List[Dict[str, str]]:
        """Obtener argumentos predefinidos de un ejecutable"""
        try:
            metadata = self._load_executable_metadata()
            exe_key = f"{project_id}_{exe_name}"
            
            if "executable_metadata" in metadata and exe_key in metadata["executable_metadata"]:
                return metadata["executable_metadata"][exe_key].get("arguments", [])
            
            return []
        
        except Exception as e:
            print(f"Error loading executable arguments: {e}")
            return []

    def update_executable_arguments(self, project_id: str, exe_name: str, arguments: List[Dict[str, str]]) -> Dict[str, str]:
        """Actualizar argumentos predefinidos de un ejecutable"""
        try:
            metadata = self._load_executable_metadata()
            exe_key = f"{project_id}_{exe_name}"
            
            # Crear estructura si no existe
            if "executable_metadata" not in metadata:
                metadata["executable_metadata"] = {}
            if exe_key not in metadata["executable_metadata"]:
                metadata["executable_metadata"][exe_key] = {}
            
            # Validar formato de argumentos
            for arg in arguments:
                if not isinstance(arg, dict) or "description" not in arg or "arguments" not in arg:
                    return {"status": "error", "message": "Formato de argumentos inválido. Debe ser [{'description': '...', 'arguments': '...'}]"}
            
            # Actualizar argumentos
            metadata["executable_metadata"][exe_key]["arguments"] = arguments
            metadata["executable_metadata"][exe_key]["updated_date"] = datetime.now().isoformat() + "Z"
            
            self._save_executable_metadata(metadata)
            return {"status": "success", "message": "Argumentos actualizados exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error actualizando argumentos: {str(e)}"}

    def get_all_project_executables(self, project_id: str) -> List[Dict[str, Any]]:
        """Obtener TODOS los ejecutables de un proyecto (incluyendo ocultos) para gestión"""
        project = self.get_csharp_project(project_id)
        if not project:
            return []
        
        project_dir = project["directory"]
        if not os.path.isdir(project_dir):
            return []

        executables = []
        metadata = self._load_executable_metadata()
        
        # Buscar en bin/Release y bin/Debug
        search_patterns = [
            os.path.join(project_dir, "**/bin/Release/**/*.exe"),
            os.path.join(project_dir, "**/bin/Debug/**/*.exe")
        ]
        
        found_exe_files = set()
        for pattern in search_patterns:
            for exe_path in glob.glob(pattern, recursive=True):
                if os.path.isfile(exe_path):
                    found_exe_files.add(exe_path)
        
        for exe_path in found_exe_files:
            exe_name = os.path.basename(exe_path)
            exe_key = f"{project_id}_{exe_name}"
            
            # Obtener metadatos o crear por defecto
            exe_metadata = metadata.get("executable_metadata", {}).get(exe_key, {})
            
            # Determinar si es Debug o Release
            build_type = "Release" if "\\Release\\" in exe_path or "/Release/" in exe_path else "Debug"
            
            executables.append({
                "filename": exe_name,
                "full_path": exe_path,
                "display_name": exe_metadata.get("display_name", exe_name.replace('.exe', '')),
                "short_description": exe_metadata.get("short_description", f"Aplicación C# ({build_type})"),
                "long_description": exe_metadata.get("long_description", ""),
                "build_type": build_type,
                "relative_path": os.path.relpath(exe_path, project_dir),
                "hidden": exe_metadata.get("hidden", False)
            })
        
        # Ordenar por nombre de display
        executables.sort(key=lambda x: x['display_name'])
        return executables

    def get_executable_metadata(self, project_id: str, exe_name: str) -> Dict[str, Any]:
        """Obtener metadatos de un ejecutable específico"""
        metadata = self._load_executable_metadata()
        exe_key = f"{project_id}_{exe_name}"
        return metadata.get("executable_metadata", {}).get(exe_key, {
            "display_name": exe_name.replace('.exe', ''),
            "short_description": "Aplicación C#",
            "long_description": "",
            "hidden": False
        })

    def update_executable_metadata(self, project_id: str, exe_name: str, metadata_update: Dict[str, Any]) -> Dict[str, str]:
        """Actualizar metadatos de un ejecutable específico"""
        try:
            metadata = self._load_executable_metadata()
            exe_key = f"{project_id}_{exe_name}"
            
            if "executable_metadata" not in metadata:
                metadata["executable_metadata"] = {}
            
            if exe_key not in metadata["executable_metadata"]:
                metadata["executable_metadata"][exe_key] = {}
            
            # Actualizar campos
            metadata["executable_metadata"][exe_key].update(metadata_update)
            
            self._save_executable_metadata(metadata)
            return {"status": "success", "message": "Metadatos actualizados exitosamente"}
        
        except Exception as e:
            return {"status": "error", "message": f"Error actualizando metadatos: {str(e)}"}

    def _save_executable_metadata(self, metadata: Dict[str, Any]):
        """Guardar metadatos de ejecutables a archivo"""
        try:
            with open(self.script_metadata_path, 'w', encoding='utf-8') as f:
                json.dump(metadata, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"Error saving C# executable metadata: {e}")

    def _cleanup_metadata_for_project(self, project_id: str):
        """Limpiar metadatos de un proyecto eliminado"""
        try:
            metadata = self._load_executable_metadata()
            if "executable_metadata" in metadata:
                # Remover metadatos que empiecen con project_id_
                keys_to_remove = [key for key in metadata["executable_metadata"].keys() 
                                if key.startswith(f"{project_id}_")]
                for key in keys_to_remove:
                    del metadata["executable_metadata"][key]
                self._save_executable_metadata(metadata)
        except Exception as e:
            print(f"Error cleaning metadata for project {project_id}: {e}")

    def _cleanup_favorites_for_project(self, project_id: str):
        """Limpiar favoritos de un proyecto eliminado"""
        try:
            with open(self.favorites_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            # Filtrar favoritos que no sean del proyecto eliminado
            data["favorites"] = [fav for fav in data.get("favorites", []) 
                               if fav.get("project_id") != project_id]
            
            with open(self.favorites_path, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"Error cleaning favorites for project {project_id}: {e}")

    def find_solution_file(self, project_id: str) -> Optional[str]:
        """
        Buscar archivo .sln en el directorio del proyecto.
        Prioridades:
        1. Si hay un solo .sln, retornarlo
        2. Si hay múltiples .sln, buscar uno que coincida con el nombre del directorio
        3. Si no hay coincidencias exactas, retornar el primero alfabéticamente
        4. Si no hay .sln, retornar None
        """
        try:
            project = self.get_csharp_project(project_id)
            if not project:
                return None
            
            project_dir = project["directory"]
            if not os.path.isdir(project_dir):
                return None
            
            # Buscar archivos .sln en el directorio raíz del proyecto
            sln_pattern = os.path.join(project_dir, "*.sln")
            sln_files = glob.glob(sln_pattern)
            
            if not sln_files:
                return None
            
            if len(sln_files) == 1:
                return sln_files[0]
            
            # Si hay múltiples archivos, buscar uno que coincida con el nombre del directorio
            project_name = os.path.basename(project_dir.rstrip(os.sep))
            for sln_file in sln_files:
                sln_name = os.path.splitext(os.path.basename(sln_file))[0]
                if sln_name.lower() == project_name.lower():
                    return sln_file
            
            # Si no hay coincidencias exactas, retornar el primero alfabéticamente
            return sorted(sln_files)[0]
            
        except Exception as e:
            print(f"Error finding solution file for project {project_id}: {e}")
            return None