# app/xulcan/tools/executors/local.py

import json
import asyncio
import logging
import inspect
from typing import Dict, Callable, Optional

from xulcan.tools.base import BaseToolExecutor
from xulcan.protocol.tools import ToolCall, ToolDefinition
from xulcan.protocol.message import ToolMessage

# ¡NUEVO! Importamos la memoria
from xulcan.kernel.environment import SystemEnvironment

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

class LocalPythonExecutor(BaseToolExecutor):
    """Adapter that executes registered Python functions in the same process.
    Supports smart dependency injection for OS-level features (StateStore, RunID).
    """

    # CIRUGÍA 1: Inyectamos el StateStore al adaptador
    def __init__(self, environment: Optional[SystemEnvironment] = None):
        self.environment = environment
        self._functions: Dict[str, Callable] = {}
        self._definitions: Dict[str, ToolDefinition] = {} 

    def register_function(self, definition: ToolDefinition, func: Callable) -> None:
        name = definition.function.name
        self._functions[name] = func
        self._definitions[name] = definition
        logger.info(f"🔧 Local function 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"▶️  LOCAL EXEC: {call.name}")
        
        if call.name not in self._functions:
            return ToolMessage(
                tool_call_id=call.id,
                name=call.name,
                content=json.dumps({"error": f"Function '{call.name}' not found."})
            )
            
        try:
            # ==========================================
            # CIRUGÍA 2: 🔒 EXTRACCIÓN DEL RUN_ID REAL
            # ==========================================
            context = getattr(call, "context", {}) or {}
            run_id = context.get("run_id", f"fallback_{call.id}")

            func = self._functions[call.name]
            clean_args = dict(call.arguments)
            
            # ==========================================
            # CIRUGÍA 3: 💉 INYECCIÓN INTELIGENTE (Xulcan Superpower)
            # ==========================================
            # Inspeccionamos qué parámetros pide la función de Python.
            # Si pide el 'run_id' o el 'state_store', ¡el OS se los inyecta mágicamente!
            # Esto evita que el LLM tenga que adivinar o generar estas variables.
            sig = inspect.signature(func)
            
            if "run_id" in sig.parameters:
                clean_args["run_id"] = run_id
                
            # ¡NUEVO! Inyectamos la Regleta completa si la función la pide
            if "environment" in sig.parameters and self.environment:
                clean_args["environment"] = self.environment
                
            # (Mantenemos esta por si alguna herramienta vieja pide solo el state_store)
            if "state_store" in sig.parameters and self.environment and self.environment.state_store:
                clean_args["state_store"] = self.environment.state_store

            # Ejecutamos la función
            if asyncio.iscoroutinefunction(func):
                result = await func(**clean_args)
            else:
                result = func(**clean_args)
            
            logger.info(f"✅ LOCAL RESULT ({call.name}): Completado exitosamente.")
            content = json.dumps(result)
            
        except Exception as e:
            logger.error(f"❌ LOCAL ERROR ({call.name}): {str(e)}", exc_info=True)
            content = json.dumps({"error": str(e)})

        return ToolMessage(
            tool_call_id=call.id, 
            name=call.name, 
            content=content
        )