import logging
from typing import Dict, List, Optional

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

from xulcan.kernel.environment import SystemEnvironment
from xulcan.utils.template import render_template


logger = logging.getLogger("xulcan.tools.registry")
class ToolRouterExecutor(BaseToolExecutor):
    """
    Acts as the Main Registry AND the Main Executor for the Kernel.
    Routes incoming ToolCalls to the appropriate specific Executor (Adapter).
    """
    
    def __init__(self, environment: Optional[SystemEnvironment] = None) -> None:
        self.environment = environment
        self._routing_table: Dict[str, BaseToolExecutor] = {}

    def get_definitions(self, tool_names: list[str]) -> list[ToolDefinition]:
        """Returns the JSON schemas for the requested tools."""
        definitions = []
        for name in tool_names:
            if name in self._routing_table:
                executor = self._routing_table[name]
                
                # Verificamos si el ejecutor sabe devolver una definición
                # (Soporta múltiples herramientas en un ejecutor como el tuyo)
                if hasattr(executor, 'get_definition'):
                    schema = executor.get_definition(name)
                    if schema:
                        definitions.append(schema)
                    else:
                        logger.warning(f"⚠️ Tool '{name}' routed, but executor returned empty schema.")
                
                # (O soporte legado para ejecutores 1 a 1 que guardan la prop directa)
                elif hasattr(executor, 'definition'):
                    definitions.append(executor.definition)
                else:
                    logger.warning(f"⚠️ Tool '{name}' has no definition logic in its executor.")
            else:
                logger.warning(f"⚠️ Tool '{name}' requested by Blueprint but not routed.")
                
        return definitions

    def route_tool(self, tool_name: str, executor: BaseToolExecutor) -> None:
        """Bind a specific tool name to a specific execution adapter."""
        self._routing_table[tool_name] = executor
        logger.debug(f"🔀 Route mapped: '{tool_name}' -> {executor.__class__.__name__}")

    async def execute(self, call: ToolCall) -> ToolMessage:
        if call.name not in self._routing_table:
            return ToolMessage(tool_call_id=call.id, name=call.name, content=f'{{"error": "Herramienta no encontrada."}}')
            
        executor = self._routing_table[call.name]
        
        try:
            # ==========================================
            # 🪄 MAGIA JINJA2: TRADUCCIÓN DE ARGUMENTOS
            # ==========================================
            if self.environment and self.environment.state_store:
                context_meta = getattr(call, "context", {}) or {}
                run_id = context_meta.get("run_id")
                
                if run_id:
                    args_str = str(call.arguments)
                    if "{{" in args_str:
                        # Obtenemos toda la memoria
                        keys = await self.environment.state_store.keys(run_id)
                        memory_dict = {k: await self.environment.state_store.get(run_id, k) for k in keys}
                        
                        # Reemplazamos SOLO los strings que tengan {{ }}
                        # En ToolRouterExecutor.execute(...)
                        new_args = {}
                        for k, v in call.arguments.items():
                            if isinstance(v, str):
                                # ❌ ANTES: Renderizaba todo a ciegas
                                # new_args[k] = render_template(v, memory_dict)
                                
                                # ✅ AHORA: Solo renderiza si hay una variable Jinja
                                if "{{" in v:
                                    try:
                                        new_args[k] = render_template(v, memory_dict)
                                    except Exception as template_err:
                                        logger.error(f"⚠️ Error Jinja en Router: {template_err}. Pasando string crudo.")
                                        new_args[k] = v # Fallback seguro
                                else:
                                    new_args[k] = v
                            else:
                                new_args[k] = v
                                
                        # Inmutabilidad: Creamos una copia del ToolCall con los nuevos argumentos
                        call = call.model_copy(update={"arguments": new_args})
                        logger.info(f"🪄 ToolRouter: Argumentos traducidos con memoria para '{call.name}'.")

            # Delegamos al ejecutor final (Sandbox, Local, etc)
            return await executor.execute(call)
            
        except Exception as e:
            logger.error(f"💥 Error en Router ({call.name}): {str(e)}", exc_info=True)
            return ToolMessage(tool_call_id=call.id, name=call.name, content=f'{{"error": "{str(e)}"}}')