"""
Agent Orchestrator for Socrates AI

Coordinates all agents and manages their interactions, including:
- Agent initialization
- Request routing
- Knowledge base management
- Database components
- Event emission for decoupled communication
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import TYPE_CHECKING, Any

from socratic_nexus.clients import ClaudeClient

from socratic_system.config import SocratesConfig
from socratic_system.database import VectorDatabase
from socratic_system.events import EventEmitter, EventType
from socratic_system.models import KnowledgeEntry
from socratic_system.security.agent_identity import AgentIdentityManager
from socratic_system.security.audit_logger import AuditLogger
from socratic_system.security.sandbox import Sandbox, SandboxConfig

# Import Socratic-Morality governance framework
try:
    from socratic_morality import Constitution, Governor
except ImportError:
    Governor = None
    Constitution = None

# Type hints for lazy-loaded agents
if TYPE_CHECKING:
    from socratic_agents import (
        CodeGeneratorAgent,
        CodeValidationAgent,
        ConflictDetectorAgent,
        ContextAnalyzerAgent,
        DocumentProcessorAgent,
        KnowledgeAnalysisAgent,
        KnowledgeManagerAgent,
        MultiLLMAgent,
        NoteManagerAgent,
        ProjectManagerAgent,
        QualityControllerAgent,
        QuestionQueueAgent,
        SocraticCounselorAgent,
        SystemMonitorAgent,
        UserLearningAgent,
        UserManagerAgent,
    )


class AgentOrchestrator:
    """
    Orchestrates all agents and manages system-wide coordination.

    Supports both old-style initialization (api_key string) and new-style (SocratesConfig)
    for backward compatibility.
    """

    def __init__(self, api_key_or_config: str | SocratesConfig):
        """
        Initialize the orchestrator.

        Args:
            api_key_or_config: Either an API key string (old style) or SocratesConfig (new style)
        """
        # Apply runtime patches to unify encryption before initializing any clients
        from socratic_system.patches import apply_all_patches

        apply_all_patches()

        # Handle both old-style (api_key string) and new-style (SocratesConfig) initialization
        if isinstance(api_key_or_config, str):
            # Old style: create config from API key with defaults
            self.config = SocratesConfig(api_key=api_key_or_config)
        else:
            # New style: use provided config
            self.config = api_key_or_config

        self.api_key = self.config.api_key

        # Initialize logging using the DebugLogger system
        from socratic_system.utils.logger import get_logger as get_debug_logger

        self.logger = get_debug_logger("orchestrator")

        # Initialize sandbox configuration
        self._sandbox_config = SandboxConfig(
            timeout_seconds=60,
            max_memory_mb=512,
            max_file_handles=10,
            project_dir=str(self.config.data_dir),
            allow_file_write=True,
            allow_network=False,
        )
        self._sandbox = None  # Lazy-loaded

        # Initialize event emitter
        self.event_emitter = EventEmitter()

        # Phase 2a: Initialize Governor for ethical governance and constitutional AI
        # Must be initialized before agent bus so Governor can validate requests
        self._initialize_governor()
        self.logger.info("Governor and constitutional framework initialized (Phase 2a)")

        # Phase 2b: Initialize audit logging for immutable tracking
        self.audit_logger = AuditLogger(
            db_connection=None,  # Will be set after database initialization
            logger=self.logger,
            retention_days=730,  # 2 years
            encrypt_at_rest=True,
        )
        self.logger.info("Audit logger initialized (Phase 2b)")

        # Phase 2b: Initialize agent identity manager for zero-trust
        # Use API key as base for signing (in production, use dedicated key)
        secret_key = self.api_key or "default_secret_key_development_only"
        self.identity_manager = AgentIdentityManager(
            secret_key=secret_key, logger=self.logger, token_lifetime_hours=24
        )
        self.logger.info("Agent identity manager initialized (Phase 2b)")

        # Phase 2: Initialize agent bus and registry for message routing
        from socratic_system.messaging.agent_bus import AgentBus
        from socratic_system.messaging.agent_registry import AgentRegistry

        self.agent_registry = AgentRegistry(health_check_timeout=60)
        self.agent_bus = AgentBus(
            event_emitter=self.event_emitter,
            registry=self.agent_registry,
            governor=self.governor,
            logger=self.logger,
            audit_logger=self.audit_logger,
            max_concurrent_requests=100,
            default_timeout=30.0,
        )
        self.logger.info("Agent bus and registry initialized with Governor validation (Phase 2)")

        # Initialize database components with configured paths
        self.logger.info("Initializing database components...")

        # Use unified DatabaseSingleton for both CLI and API
        from socrates_api.database import DatabaseSingleton

        DatabaseSingleton.initialize(str(self.config.projects_db_path))
        self.database = DatabaseSingleton.get_instance()

        # Connect audit logger to database
        self.audit_logger.db = self.database
        self.logger.debug("Audit logger connected to database")

        self.vector_db = VectorDatabase(
            str(self.config.vector_db_path), embedding_model=self.config.embedding_model
        )
        self.logger.info("Database components initialized successfully")

        # Initialize Claude client
        self.claude_client = ClaudeClient(
            self.config.api_key, self, subscription_token=self.config.subscription_token
        )

        # Cache for lazy-loaded agents
        self._agents_cache: dict[str, Any] = {}

        # Start background knowledge base loading (non-blocking)
        # Skip in test mode to avoid SQLite deadlocks from multiple threads
        import os
        import threading

        self.knowledge_loaded = False
        self._knowledge_thread = None

        # Only start knowledge loading thread if not in test mode
        if "PYTEST_CURRENT_TEST" not in os.environ:
            self._knowledge_thread = threading.Thread(
                target=self._load_knowledge_base_safe, daemon=True
            )
            self._knowledge_thread.start()
        else:
            # In test mode, mark as loaded immediately (tests use mocks)
            self.knowledge_loaded = True

        # Phase 3: Initialize caching and background handlers for non-blocking processing
        from socratic_system.caching import InMemoryAnalysisCache
        from socratic_system.handlers import BackgroundHandlers
        from socratic_system.jobs import JobTracker

        self.cache = InMemoryAnalysisCache()
        self.job_tracker = JobTracker()
        self.background_handlers = BackgroundHandlers(
            orchestrator=self, cache=self.cache, job_tracker=self.job_tracker
        )
        self.logger.info("Analysis caching and background handlers initialized (Phase 3)")

        # Phase 4: Pre-initialize all agents for agent bus discovery
        # This ensures handlers are registered before any endpoints call agent_bus.send_request()
        self._initialize_all_agents()

        # Emit system initialized event
        self.event_emitter.emit(
            EventType.SYSTEM_INITIALIZED,
            {
                "version": "0.5.0",
                "data_dir": str(self.config.data_dir),
                "model": self.config.claude_model,
            },
        )

        # Log initialization summary
        self.logger.info("=" * 70)
        self.logger.info("Socrates AI initialized successfully!")
        self.logger.info(f"  Configuration: {self.config}")
        self.logger.info(f"  Projects DB: {self.config.projects_db_path}")
        self.logger.info(f"  Vector DB: {self.config.vector_db_path}")
        self.logger.info("=" * 70)

    def _initialize_governor(self) -> None:
        """
        Initialize the Governor and constitutional framework for ethical governance.

        Loads the constitution.yaml file and initializes the Governor engine
        for validating agent actions against ethical principles.
        """
        if Governor is None or Constitution is None:
            self.logger.warning("Socratic-Morality not available - Governor initialization skipped")
            self.governor = None
            return

        try:
            # Load constitution.yaml from the project root
            constitution_path = Path(__file__).parent.parent.parent / "constitution.yaml"

            if not constitution_path.exists():
                self.logger.warning(
                    f"Constitution file not found at {constitution_path} - Governor skipped"
                )
                self.governor = None
                return

            self.logger.debug(f"Loading constitution from {constitution_path}")

            # Load constitution from YAML file
            self.constitution = Constitution.load_from_file(str(constitution_path))

            # Initialize Governor with constitution
            self.governor = Governor(self.constitution)

            self.logger.info("Governor initialized successfully with constitutional framework")
            self.logger.debug(f"  Supreme Principle: {self.constitution.supreme_principle}")
            self.logger.debug(f"  Principles: {len(self.constitution.principles)}")
            if hasattr(self.constitution, "rules"):
                self.logger.debug(f"  Rules: {len(self.constitution.rules)}")

        except Exception as e:
            self.logger.error(f"Failed to initialize Governor: {e}")
            self.governor = None

    def _load_knowledge_base_safe(self) -> None:
        """Wrapper for knowledge base loading in background thread with error handling"""
        try:
            self._load_knowledge_base()
        except Exception as e:
            self.logger.error(f"Background knowledge loading failed: {e}")

    def wait_for_knowledge(self, timeout: int = 10) -> bool:
        """
        Wait for knowledge base to finish loading (optional blocking method)

        Args:
            timeout: Maximum seconds to wait before returning

        Returns:
            True if knowledge loaded successfully, False if timeout
        """
        if self.knowledge_loaded:
            return True

        # If thread exists, wait for it; otherwise already loaded (test mode)
        if self._knowledge_thread is not None:
            self._knowledge_thread.join(timeout=timeout)
        return self.knowledge_loaded

    def _initialize_all_agents(self) -> None:
        """Pre-initialize all agents and register them with the agent bus.

        This ensures handlers are available when endpoints call agent_bus.send_request().
        Each agent is initialized and then explicitly registered with the agent registry.
        """
        try:
            self.logger.info("Pre-initializing all agents for agent bus registration...")

            # Mapping of property names to expected registry names
            # Property names follow snake_case and are used by external APIs
            agent_name_map = {
                "project_manager": "project_manager",
                "socratic_counselor": "socratic_counselor",
                "context_analyzer": "context_analyzer",
                "code_generator": "code_generator",
                "system_monitor": "system_monitor",
                "conflict_detector": "conflict_detector",
                "document_processor": "document_processor",
                "user_manager": "user_manager",
                "note_manager": "note_manager",
                "knowledge_manager": "knowledge_manager",
                "knowledge_analysis": "knowledge_analysis",
                "quality_controller": "quality_controller",
                "learning_agent": "learning",  # API calls use "learning", not "learning_agent"
                "multi_llm_agent": "multi_llm_manager",  # llm_config.py uses "multi_llm_manager"
                "question_queue": "question_queue",
                "code_validation_agent": "code_validation",  # API calls use "code_validation"
            }

            for property_name, registry_name in agent_name_map.items():
                try:
                    agent = getattr(self, property_name)
                    self.logger.debug(f"Initialized agent: {agent.name}")

                    # Register agent with the agent registry so it can be discovered
                    # Note: agent.process() is synchronous, not async
                    self.agent_registry.register(
                        agent_name=registry_name,
                        handler=agent.process,
                        capabilities=[],
                        metadata={"agent_class": agent.__class__.__name__},
                        supports_sync=True,
                        supports_async=False,  # agent.process() is synchronous
                    )
                    self.logger.debug(
                        f"Registered agent '{agent.name}' with registry as '{registry_name}'"
                    )
                except Exception as e:
                    self.logger.warning(f"Failed to initialize agent {property_name}: {e}")

            # Verify agents are registered with the bus
            registered_count = self.agent_registry.count()
            self.logger.info(f"Agent initialization complete: {registered_count} agents registered")

        except Exception as e:
            self.logger.error(f"Error during agent pre-initialization: {e}")

    @property
    def sandbox(self) -> Sandbox:
        """Get or create sandbox instance for code execution.

        Returns:
            Configured Sandbox instance
        """
        if self._sandbox is None:
            self._sandbox = Sandbox(self._sandbox_config, logger=self.logger)
        return self._sandbox

    # Lazy-loaded agent properties (imported from socratic_agents library 0.3.5+)
    # Note: Agents expect orchestrator parameter only (not individual services)
    @property
    def project_manager(self) -> ProjectManagerAgent:
        """Lazy-load project manager agent"""
        if "project_manager" not in self._agents_cache:
            from socratic_agents import ProjectManagerAgent

            self._agents_cache["project_manager"] = ProjectManagerAgent(self)
        return self._agents_cache["project_manager"]

    @property
    def socratic_counselor(self) -> SocraticCounselorAgent:
        """Lazy-load socratic counselor agent"""
        if "socratic_counselor" not in self._agents_cache:
            from socratic_agents import SocraticCounselorAgent

            self._agents_cache["socratic_counselor"] = SocraticCounselorAgent(self)
        return self._agents_cache["socratic_counselor"]

    @property
    def context_analyzer(self) -> ContextAnalyzerAgent:
        """Lazy-load context analyzer agent"""
        if "context_analyzer" not in self._agents_cache:
            from socratic_agents import ContextAnalyzerAgent

            self._agents_cache["context_analyzer"] = ContextAnalyzerAgent(self)
        return self._agents_cache["context_analyzer"]

    @property
    def code_generator(self) -> CodeGeneratorAgent:
        """Lazy-load code generator agent with sandbox integration"""
        if "code_generator" not in self._agents_cache:
            from socratic_agents import CodeGeneratorAgent

            from socratic_system.agents.code_generator_sandbox_wrapper import (
                CodeGeneratorSandboxWrapper,
            )

            base_agent = CodeGeneratorAgent(self)
            self._agents_cache["code_generator"] = CodeGeneratorSandboxWrapper(
                base_agent=base_agent, sandbox=self.sandbox, audit_logger=self.audit_logger
            )
        return self._agents_cache["code_generator"]

    @property
    def system_monitor(self) -> SystemMonitorAgent:
        """Lazy-load system monitor agent"""
        if "system_monitor" not in self._agents_cache:
            from socratic_agents import SystemMonitorAgent

            self._agents_cache["system_monitor"] = SystemMonitorAgent(self)
        return self._agents_cache["system_monitor"]

    @property
    def conflict_detector(self) -> ConflictDetectorAgent:
        """Lazy-load conflict detector agent"""
        if "conflict_detector" not in self._agents_cache:
            from socratic_agents import ConflictDetectorAgent

            self._agents_cache["conflict_detector"] = ConflictDetectorAgent(self)
        return self._agents_cache["conflict_detector"]

    @property
    def document_processor(self) -> DocumentProcessorAgent:
        """Lazy-load document processor agent"""
        if "document_processor" not in self._agents_cache:
            from socratic_agents import DocumentProcessorAgent

            self._agents_cache["document_processor"] = DocumentProcessorAgent(self)
        return self._agents_cache["document_processor"]

    @property
    def user_manager(self) -> UserManagerAgent:
        """Lazy-load user manager agent"""
        if "user_manager" not in self._agents_cache:
            from socratic_agents import UserManagerAgent

            self._agents_cache["user_manager"] = UserManagerAgent(self)
        return self._agents_cache["user_manager"]

    @property
    def note_manager(self) -> NoteManagerAgent:
        """Lazy-load note manager agent"""
        if "note_manager" not in self._agents_cache:
            from socratic_agents import NoteManagerAgent

            self._agents_cache["note_manager"] = NoteManagerAgent(self)
        return self._agents_cache["note_manager"]

    @property
    def knowledge_manager(self) -> KnowledgeManagerAgent:
        """Lazy-load knowledge manager agent"""
        if "knowledge_manager" not in self._agents_cache:
            from socratic_agents import KnowledgeManagerAgent

            self._agents_cache["knowledge_manager"] = KnowledgeManagerAgent(
                "KnowledgeManager", self
            )
        return self._agents_cache["knowledge_manager"]

    @property
    def knowledge_analysis(self) -> KnowledgeAnalysisAgent:
        """Lazy-load knowledge analysis agent"""
        if "knowledge_analysis" not in self._agents_cache:
            from socratic_agents import KnowledgeAnalysisAgent

            self._agents_cache["knowledge_analysis"] = KnowledgeAnalysisAgent(self)
        return self._agents_cache["knowledge_analysis"]

    @property
    def quality_controller(self) -> QualityControllerAgent:
        """Lazy-load quality controller agent"""
        if "quality_controller" not in self._agents_cache:
            from socratic_agents import QualityControllerAgent

            self._agents_cache["quality_controller"] = QualityControllerAgent(self)
        return self._agents_cache["quality_controller"]

    @property
    def learning_agent(self) -> UserLearningAgent:
        """Lazy-load user learning agent"""
        if "learning_agent" not in self._agents_cache:
            from socratic_agents import UserLearningAgent

            self._agents_cache["learning_agent"] = UserLearningAgent(self)
        return self._agents_cache["learning_agent"]

    @property
    def multi_llm_agent(self) -> MultiLLMAgent:
        """Lazy-load multi-LLM agent"""
        if "multi_llm_agent" not in self._agents_cache:
            from socratic_agents import MultiLLMAgent

            self._agents_cache["multi_llm_agent"] = MultiLLMAgent(self)
        return self._agents_cache["multi_llm_agent"]

    @property
    def question_queue(self) -> QuestionQueueAgent:
        """Lazy-load question queue agent"""
        if "question_queue" not in self._agents_cache:
            from socratic_agents import QuestionQueueAgent

            self._agents_cache["question_queue"] = QuestionQueueAgent(self)
        return self._agents_cache["question_queue"]

    @property
    def code_validation_agent(self) -> CodeValidationAgent:
        """Lazy-load code validation agent"""
        if "code_validation_agent" not in self._agents_cache:
            from socratic_agents import CodeValidationAgent

            self._agents_cache["code_validation_agent"] = CodeValidationAgent(self)
        return self._agents_cache["code_validation_agent"]

    def _load_knowledge_base(self) -> None:
        """Load default knowledge base from config file if not already loaded"""
        if self.vector_db.knowledge_loaded:
            self.logger.debug("Knowledge base already loaded, skipping initialization")
            return

        self.logger.info("Loading knowledge base...")
        self.event_emitter.emit(EventType.LOG_INFO, {"message": "Loading knowledge base..."})

        # Load knowledge data from config file
        knowledge_data = self._load_knowledge_config()

        if not knowledge_data:
            self._emit_no_knowledge_warning()
            return

        # Process and add knowledge entries
        loaded_count, error_count = self._process_knowledge_entries(knowledge_data)

        # Mark knowledge base as loaded
        self.vector_db.knowledge_loaded = True
        self.knowledge_loaded = True

        # Emit completion event
        self._emit_knowledge_loaded_event(loaded_count, error_count)

    def _load_knowledge_config(self) -> list:
        """Load knowledge configuration from file"""
        # Determine config path
        if self.config.knowledge_base_path:
            config_path = Path(self.config.knowledge_base_path)
            source = "configured path"
        else:
            config_path = Path(__file__).parent.parent / "config" / "knowledge_base.json"
            source = "default location"

        self.logger.debug(f"Attempting to load knowledge base from {source}: {config_path}")

        return self._read_knowledge_config_file(config_path, source)

    def _read_knowledge_config_file(self, config_path: Path, source: str) -> list:
        """Read and parse knowledge config file"""
        try:
            if not config_path.exists():
                self.logger.debug(f"Knowledge config not found at {source}: {config_path}")
                return []

            self.logger.debug(f"Knowledge base file found at: {config_path}")
            with open(config_path, encoding="utf-8") as f:
                config = json.load(f)

            knowledge_entries = config.get("default_knowledge", [])
            if knowledge_entries:
                self.logger.info(
                    f"Successfully loaded {len(knowledge_entries)} knowledge entries from {source}"
                )
                return knowledge_entries
            else:
                self.logger.warning(f"No 'default_knowledge' entries found in config at {source}")
                return []

        except json.JSONDecodeError as e:
            self.logger.error(f"Invalid JSON in knowledge config at {config_path}: {e}")
            return []
        except Exception as e:
            self.logger.error(f"Failed to load knowledge config from {config_path}: {e}")
            return []

    def _process_knowledge_entries(self, knowledge_data: list) -> tuple:
        """Process and add knowledge entries to database"""
        self.logger.info(f"Found {len(knowledge_data)} knowledge entries to load")

        loaded_count = 0
        error_count = 0

        for entry_data in knowledge_data:
            if self._add_knowledge_entry(entry_data):
                loaded_count += 1
            else:
                error_count += 1

        return loaded_count, error_count

    def _add_knowledge_entry(self, entry_data: dict) -> bool:
        """Add single knowledge entry to both vector and SQL databases"""
        try:
            entry = KnowledgeEntry(**entry_data)
            self.vector_db.add_knowledge(entry)

            # Also store in SQL database for persistence and querying
            self.database.save_knowledge_document(
                user_id="system",
                project_id="default",
                doc_id=entry.id,
                title=getattr(entry, "category", "Knowledge Entry"),
                content=entry.content,
                source="hardcoded_knowledge_base",
                document_type="knowledge_entry",
            )
            return True

        except TypeError as e:
            # Handle type errors (e.g., NoneType errors) with more detail
            entry_id = entry_data.get("id", "unknown")
            self.logger.error(
                f"Type error adding knowledge entry '{entry_id}': {e}. "
                f"Entry data keys: {list(entry_data.keys())}"
            )
            return False
        except Exception as e:
            self.logger.error(
                f"Failed to add knowledge entry '{entry_data.get('id', 'unknown')}': {e}"
            )
            return False

    def _emit_no_knowledge_warning(self) -> None:
        """Emit warning when no knowledge base config found"""
        self.logger.warning(
            "No knowledge base config found - system will run with empty knowledge base"
        )
        self.event_emitter.emit(
            EventType.LOG_WARNING, {"message": "No knowledge base config found"}
        )

    def _emit_knowledge_loaded_event(self, loaded_count: int, error_count: int) -> None:
        """Emit knowledge loaded event with summary"""
        summary = f"Knowledge base loaded: {loaded_count} entries added"
        if error_count > 0:
            summary += f" ({error_count} failed)"
        self.logger.info(summary)

        self.event_emitter.emit(
            EventType.KNOWLEDGE_LOADED,
            {
                "entry_count": loaded_count,
                "error_count": error_count,
                "status": "success" if error_count == 0 else "partial",
            },
        )

    def set_model(self, model_name: str) -> bool:
        """
        Update the Claude model at runtime.

        Args:
            model_name: The new model name to use

        Returns:
            True if successful, False otherwise
        """
        try:
            self.config.claude_model = model_name
            self.claude_client.model = model_name
            self.logger.info(f"Model updated to {model_name}")
            return True
        except Exception as e:
            self.logger.error(f"Error updating model: {e}")
            return False

    def process_request(self, agent_name: str, request: dict[str, Any]) -> dict[str, Any]:
        """
        Route a request to the appropriate agent (synchronous).

        Args:
            agent_name: Name of the agent to process the request
            request: Dictionary containing the request parameters

        Returns:
            Dictionary containing the agent's response

        Example:
            >>> result = orchestrator.process_request('project_manager', {
            ...     'action': 'create_project',
            ...     'project_name': 'My Project',
            ...     'owner': 'alice'
            ... })
        """
        agents = {
            "project_manager": self.project_manager,
            "socratic_counselor": self.socratic_counselor,
            "context_analyzer": self.context_analyzer,
            "code_generator": self.code_generator,
            "system_monitor": self.system_monitor,
            "conflict_detector": self.conflict_detector,
            "document_agent": self.document_processor,
            "user_manager": self.user_manager,
            "note_manager": self.note_manager,
            "knowledge_manager": self.knowledge_manager,
            "quality_controller": self.quality_controller,
            "learning": self.learning_agent,
            "multi_llm": self.multi_llm_agent,
            "question_queue": self.question_queue,
            "code_validation": self.code_validation_agent,
        }

        agent = agents.get(agent_name)
        if agent:
            self.event_emitter.emit(
                EventType.AGENT_START,
                {"agent": agent_name, "action": request.get("action", "unknown")},
            )

            try:
                result = agent.process(request)

                self.event_emitter.emit(
                    EventType.AGENT_COMPLETE,
                    {"agent": agent_name, "status": result.get("status", "unknown")},
                )

                return result
            except Exception as e:
                self.logger.error(f"Agent {agent_name} error: {e}")
                self.event_emitter.emit(
                    EventType.AGENT_ERROR, {"agent": agent_name, "error": str(e)}
                )
                raise
        else:
            return {"status": "error", "message": f"Unknown agent: {agent_name}"}

    async def process_request_async(
        self, agent_name: str, request: dict[str, Any]
    ) -> dict[str, Any]:
        """
        Route a request to the appropriate agent asynchronously.

        Allows for non-blocking execution of long-running operations. Most useful
        when multiple operations need to run concurrently or when integration with
        async frameworks (FastAPI, etc.) is needed.

        Args:
            agent_name: Name of the agent to process the request
            request: Dictionary containing the request parameters

        Returns:
            Dictionary containing the agent's response

        Raises:
            ValueError: If agent is not found

        Example:
            >>> result = await orchestrator.process_request_async('code_generator', {
            ...     'action': 'generate_code',
            ...     'project': project_context
            ... })

        Concurrent Example:
            >>> results = await asyncio.gather(
            ...     orchestrator.process_request_async('code_generator', code_req),
            ...     orchestrator.process_request_async('socratic_counselor', socratic_req)
            ... )
        """
        agents = {
            "project_manager": self.project_manager,
            "socratic_counselor": self.socratic_counselor,
            "context_analyzer": self.context_analyzer,
            "code_generator": self.code_generator,
            "system_monitor": self.system_monitor,
            "conflict_detector": self.conflict_detector,
            "document_agent": self.document_processor,
            "user_manager": self.user_manager,
            "note_manager": self.note_manager,
            "knowledge_manager": self.knowledge_manager,
            "quality_controller": self.quality_controller,
            "learning": self.learning_agent,
            "multi_llm": self.multi_llm_agent,
            "question_queue": self.question_queue,
            "code_validation": self.code_validation_agent,
        }

        agent = agents.get(agent_name)
        if not agent:
            raise ValueError(f"Unknown agent: {agent_name}")

        self.event_emitter.emit(
            EventType.AGENT_START,
            {"agent": agent_name, "action": request.get("action", "unknown"), "async": True},
        )

        try:
            result = await agent.process_async(request)

            self.event_emitter.emit(
                EventType.AGENT_COMPLETE,
                {"agent": agent_name, "status": result.get("status", "unknown"), "async": True},
            )

            return result

        except Exception as e:
            self.logger.error(f"Agent {agent_name} async error: {e}")
            self.event_emitter.emit(
                EventType.AGENT_ERROR, {"agent": agent_name, "error": str(e), "async": True}
            )
            raise

    def _safe_log(self, level: str, message: str):
        """Safely log messages, suppressing errors during Python shutdown.

        During Python interpreter shutdown, the logging module may be partially
        deinitialized, causing 'sys.meta_path is None' errors. This method
        safely handles those cases.
        """
        try:
            if level == "debug":
                self.logger.debug(message)
            elif level == "info":
                self.logger.info(message)
            elif level == "warning":
                self.logger.warning(message)
            elif level == "error":
                self.logger.error(message)
        except Exception:
            # Silently ignore logging errors during shutdown
            pass

    def close(self):
        """Close all database connections and release resources.

        This method should be called before shutting down the orchestrator
        or before deleting temporary directories to ensure all file handles
        are properly released, especially important on Windows systems.
        """
        try:
            # Wait for knowledge base loading thread to complete if it exists
            if hasattr(self, "_knowledge_thread") and self._knowledge_thread is not None:
                if self._knowledge_thread.is_alive():
                    # Give thread up to 5 seconds to finish
                    self._knowledge_thread.join(timeout=5)
                self._safe_log("debug", "Knowledge base loading thread stopped")
        except Exception as e:
            self._safe_log("warning", f"Error waiting for knowledge thread: {e}")

        try:
            # Close vector database to release ChromaDB file handles
            if hasattr(self, "vector_db") and self.vector_db is not None:
                self.vector_db.close()
                self._safe_log("info", "Vector database closed")
        except Exception as e:
            self._safe_log("warning", f"Error closing vector database: {e}")

        try:
            # Close project database
            if hasattr(self, "database") and self.database is not None:
                if hasattr(self.database, "close"):
                    self.database.close()
                self._safe_log("info", "Project database closed")
        except Exception as e:
            self._safe_log("warning", f"Error closing project database: {e}")

        try:
            # Clear agents cache
            self._agents_cache.clear()
            self._safe_log("debug", "Agents cache cleared")
        except Exception as e:
            self._safe_log("warning", f"Error clearing agents cache: {e}")

    def __del__(self):
        """Destructor to ensure cleanup when orchestrator is destroyed."""
        try:
            self.close()
        except Exception:
            # Silently ignore errors in destructor
            pass
