# app/xulcan/tools/executors/sandbox/executor.py

import json
import logging
from typing import Dict, Optional

from xulcan.tools.base import BaseToolExecutor
from xulcan.protocol.tools import ToolCall, ToolDefinition
from xulcan.protocol.message import ToolMessage
from xulcan.tools.executors.sandbox.provider import IsolationProvider

# ¡NUEVO! Importamos la interfaz del Blackboard
from xulcan.kernel.environment import SystemEnvironment 

logger = logging.getLogger("xulcan.tools.executors.sandbox")

class SandboxExecutor(BaseToolExecutor):
    """
    Adapter que conecta la FSM de Xulcan con un entorno aislado.
    Implementa Inicialización Perezosa (Lazy Init) de forma Stateless.
    """

    # CIRUGÍA 1: Inyectamos el StateStore (opcional por si estamos en un test rápido)
    def __init__(self, provider: IsolationProvider, environment: Optional[SystemEnvironment] = None) -> None:
        self.provider = provider
        self.environment = environment
        self._definitions: Dict[str, ToolDefinition] = {}

    def register_tool(self, definition: ToolDefinition) -> None:
        name = definition.function.name
        self._definitions[name] = definition
        logger.info(f"🛡️ Sandbox tool registered: {name}")

    def get_definition(self, name: str) -> Optional[ToolDefinition]:
        return self._definitions.get(name)

    async def execute(self, call: ToolCall) -> ToolMessage:
        logger.info(f"▶️  SANDBOX EXEC: {call.name}")

        if call.name not in self._definitions:
            return self._build_error_message(call, f"Herramienta '{call.name}' no registrada.")

        # ==========================================
        # CIRUGÍA 2: 🔒 EXTRACCIÓN LIMPIA DEL ID (CON PERSISTENCIA)
        # ==========================================
        context = getattr(call, "context", {}) or {}
        
        # 1. Prioridad Máxima: El Workspace persistente de la Regleta
        if self.environment and hasattr(self.environment, 'workspace_id') and self.environment.workspace_id:
            session_id = self.environment.workspace_id
        # 2. Fallback: El contenedor efímero de esta ejecución particular
        else:
            session_id = context.get("run_id") or f"fallback_{call.id}"
            
        if session_id.startswith("fallback_"):
            logger.warning(f"⚠️ ToolCall {call.id} no tiene 'run_id' en context. Usando fallback efímero.")
            
        try:
            # ==========================================
            # 🚀 LAZY INITIALIZATION (Stateless)
            # ==========================================
            if not await self.provider.is_active(session_id):
                logger.info(f"⚡ Inicializando máquina perezosa para sesión: {session_id}")
                
                # 🌟 EL PUENTE FÍSICO: Inyectamos la ruta de tu carpeta staging
                import os
                ruta_host = os.path.abspath("./xulcan_staging")
                
                await self.provider.start_session(session_id, workspace_path=ruta_host)
            # ==========================================
            # 🔀 ENRUTAMIENTO Y EJECUCIÓN ROBUSTA
            # ==========================================
            result_data = None
            
            if call.name == "sandbox_run_python":
                code = call.arguments.get("code", "")
                temp_script = "/workspace/.xulcan_temp.py"
                await self.provider.write_file(session_id, temp_script, code)
                result_data = await self.provider.execute_command(session_id, f"python3 {temp_script}")

            elif call.name == "sandbox_run_bash":
                command = call.arguments.get("command", "")
                result_data = await self.provider.execute_command(session_id, command)
                
                # 🌟 EL FIX: Si el comando bash no devuelve nada de texto, le decimos a la IA 
                # explícitamente que funcionó pero fue silencioso.
                stdout = result_data.get("stdout", "").strip()
                stderr = result_data.get("stderr", "").strip()
                
                if stdout or stderr:
                    salida = f"STDOUT:\n{stdout}\nSTDERR:\n{stderr}"
                else:
                    salida = "Comando ejecutado con éxito (Sin salida en terminal)."
                
                # Reemplaza result_data con el string formateado para que el LLM lo lea bien
                result_data = {"status": "success", "output": salida}
                
            elif call.name == "sandbox_read_file":
                filepath = call.arguments.get("file_path", "")
                content = await self.provider.read_file(session_id, filepath)
                result_data = {"status": "success", "content": content}

            elif call.name == "sandbox_write_file":
                filepath = call.arguments.get("file_path", "")
                
                # El Router ya hizo la magia Jinja2, así que simplemente lo tomamos:
                content = call.arguments.get("content", "")
                
                # (Opcional) Mantenemos el soporte por si el LLM usó la llave explícita antigua:
                if not content and "memory_key" in call.arguments and self.environment and self.environment.state_store:
                    memory_key = call.arguments["memory_key"]
                    content_raw = await self.environment.state_store.get(session_id, memory_key)
                    content = json.dumps(content_raw) if isinstance(content_raw, (dict, list)) else str(content_raw)

                await self.provider.write_file(session_id, filepath, content)
                result_data = {"status": "success", "message": f"Archivo {filepath} guardado correctamente."}
                
            else:
                raise ValueError(f"Acción de sandbox desconocida: {call.name}")

            # ==========================================
            # ✅ FORMATEO DE RESPUESTA
            # ==========================================
            logger.info(f"✅ SANDBOX RESULT ({call.name}): Completado exitosamente.")
            return ToolMessage(
                tool_call_id=call.id,
                name=call.name,
                content=json.dumps(result_data)
            )

        except Exception as e:
            logger.error(f"❌ SANDBOX ERROR ({call.name}): {str(e)}", exc_info=True)
            return self._build_error_message(call, str(e))

    def _build_error_message(self, call: ToolCall, error_str: str) -> ToolMessage:
        return ToolMessage(
            tool_call_id=call.id,
            name=call.name,
            content=json.dumps({
                "error": f"El entorno aislado falló: {error_str}",
                "hint": "Revisa la sintaxis, rutas o permisos."
            })
        )

    async def cleanup_session(self, session_id: str) -> None:
        logger.info(f"🧹 Limpiando máquina de la sesión: {session_id}")
        await self.provider.terminate_session(session_id)