
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.blueprint.schema import AgentBlueprint
from xulcan.kernel.environment import SystemEnvironment

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

class SubAgentExecutor(BaseToolExecutor):
    """Adapter that executes other Xulcan Agents as tools (Hierarchical Swarm)."""

    # 1. ELIMINAMOS el xulcan_client del constructor
    def __init__(self, environment: Optional[SystemEnvironment] = None) -> None:
        self.environment = environment
        self.kernel = None  # 👈 Nace vacío. Lo inyectaremos después.
        self._blueprints: Dict[str, AgentBlueprint] = {}
        self._definitions: Dict[str, ToolDefinition] = {}

    # 2. PATRÓN SETTER INJECTION (Late Binding)
    def bind_kernel(self, kernel) -> None:
        """Inyecta el motor cognitivo después de la instanciación para evitar dependencias circulares."""
        self.kernel = kernel
        logger.info("🔗 SubAgentExecutor vinculado exitosamente al ProtoKernel.")

    def register_agent(self, definition: ToolDefinition, blueprint: AgentBlueprint) -> None:
        name = definition.function.name
        self._blueprints[name] = blueprint
        self._definitions[name] = definition
        logger.info(f"🤖 Sub-Agent registered as tool: {name}")

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

    async def execute(self, call: ToolCall) -> ToolMessage:
        logger.info(f"▶️  SUB-AGENT EXEC: Despertando al agente '{call.name}'")
        
        if not self.kernel:
            raise RuntimeError("💥 Arquitectura inválida: El SubAgentExecutor no tiene un Kernel vinculado. Llama a bind_kernel() primero.")
            
        if call.name not in self._blueprints:
            return ToolMessage(
                tool_call_id=call.id,
                name=call.name,
                content=json.dumps({"error": f"Agent tool '{call.name}' not found."})
            )

        context = getattr(call, "context", {}) or {}
        parent_run_id = context.get("run_id")
        
        if not parent_run_id:
            logger.error("💥 Falla de trazabilidad: No se encontró run_id en el contexto.")
            parent_run_id = f"unknown_parent_{call.id}"
            
        try:
            blueprint = self._blueprints[call.name]
            prompt = call.arguments.get("prompt") or call.arguments.get("task", "")
            
            # 3. LLAMADA DIRECTA AL KERNEL (Sin pasar por el cliente)
            child_run_id, response = await self.kernel.execute_run(
                blueprint, 
                prompt,
                parent_id=parent_run_id
            )
            
            # ==========================================
            # CIRUGÍA 3: 🧠 TELETRANSPORTACIÓN DE MEMORIA (Fractal Swarm)
            # ==========================================
            output_memory_key = call.arguments.get("output_memory_key")
            
            if output_memory_key and self.environment and self.environment.state_store:
                logger.info(f"🧠 Sincronizando llave '{output_memory_key}' de Hijo a Padre...")
                child_data = await self.environment.state_store.get(child_run_id, output_memory_key)
                if child_data:
                    await self.environment.state_store.set(parent_run_id, output_memory_key, child_data)
                    content = f"✅ Tarea completada. Datos guardados en llave: '{output_memory_key}'"
                else:
                    content = f"⚠️ Tarea completada, pero no se guardó nada en '{output_memory_key}'."
            else:
                content = str(response.content) if response and response.content else "No output generated."
                
            logger.info(f"✅ SUB-AGENT RESULT ({call.name} -> Child ID: {child_run_id}): Tarea completada.")
            
        except Exception as e:
            logger.error(f"❌ SUB-AGENT ERROR ({call.name}): {str(e)}", exc_info=True)
            content = json.dumps({"error": f"El sub-agente falló con error: {str(e)}"})

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