"""
Lightweight Dependency Injection Container for Claude MPM.

This module provides a simple yet powerful dependency injection container
that supports:
- Service registration with interfaces
- Constructor injection
- Singleton and transient lifetimes
- Factory functions
- Lazy initialization
- Circular dependency detection
"""

import inspect
import threading
from abc import ABC
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Set, Type, TypeVar, Union

from .logger import get_logger
from claude_mpm.services.core.interfaces import IServiceContainer

logger = get_logger(__name__)

T = TypeVar('T')


class ServiceLifetime(Enum):
    """Service lifetime options."""
    SINGLETON = "singleton"  # One instance per container
    TRANSIENT = "transient"  # New instance per request
    SCOPED = "scoped"       # One instance per scope


class ServiceRegistration:
    """Represents a service registration in the container."""
    
    def __init__(
        self,
        service_type: Type,
        implementation: Optional[Union[Type, Callable]] = None,
        factory: Optional[Callable] = None,
        instance: Optional[Any] = None,
        lifetime: ServiceLifetime = ServiceLifetime.SINGLETON,
        dependencies: Optional[Dict[str, Type]] = None
    ):
        """
        Initialize service registration.
        
        Args:
            service_type: The interface/base type being registered
            implementation: The concrete implementation class
            factory: Factory function to create instances
            instance: Pre-created instance (for singleton registration)
            lifetime: Service lifetime management
            dependencies: Explicit dependency mapping
        """
        self.service_type = service_type
        self.implementation = implementation or service_type
        self.factory = factory
        self.instance = instance
        self.lifetime = lifetime
        self.dependencies = dependencies or {}
        self._lock = threading.Lock()
        
    def create_instance(self, container: 'DIContainer') -> Any:
        """Create an instance of the service."""
        if self.instance is not None:
            return self.instance
            
        if self.factory:
            return self.factory(container)
            
        # Get constructor parameters
        if inspect.isclass(self.implementation):
            return container.create_instance(self.implementation, self.dependencies)
        else:
            # It's already an instance or callable
            return self.implementation


class CircularDependencyError(Exception):
    """Raised when circular dependencies are detected."""
    pass


class ServiceNotFoundError(Exception):
    """Raised when a requested service is not registered."""
    pass


class ServiceScope:
    """
    Represents a service scope for scoped lifetime services.
    
    Scoped services live for the duration of the scope and are shared
    within that scope.
    """
    
    def __init__(self, container: 'DIContainer'):
        """Initialize service scope."""
        self._container = container
        self._scoped_instances: Dict[Type, Any] = {}
        self._disposed = False
        self._lock = threading.Lock()
        
    def get_scoped_instance(self, service_type: Type) -> Optional[Any]:
        """Get scoped instance if exists."""
        with self._lock:
            return self._scoped_instances.get(service_type)
            
    def set_scoped_instance(self, service_type: Type, instance: Any) -> None:
        """Store scoped instance."""
        with self._lock:
            if not self._disposed:
                self._scoped_instances[service_type] = instance
                
    def dispose(self) -> None:
        """
        Dispose of the scope and all scoped instances.
        
        Calls dispose() on instances that implement IDisposable.
        """
        with self._lock:
            if self._disposed:
                return
                
            for service_type, instance in self._scoped_instances.items():
                # Call dispose if available
                if hasattr(instance, 'dispose'):
                    try:
                        instance.dispose()
                    except Exception as e:
                        logger.error(f"Error disposing scoped service {service_type}: {e}")
                        
            self._scoped_instances.clear()
            self._disposed = True
            
    def __enter__(self):
        """Context manager entry."""
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - dispose scope."""
        self.dispose()


class DIContainer(IServiceContainer):
    """
    Enhanced Dependency Injection Container.
    
    Implements IServiceContainer interface to provide a complete
    dependency injection solution.
    
    Provides:
    - Service registration with multiple lifetime options
    - Automatic constructor injection
    - Interface to implementation mapping
    - Circular dependency detection
    - Lazy loading support
    - Scoped service support
    - Service disposal lifecycle
    - Named registrations
    - Configuration injection
    """
    
    def __init__(self):
        """Initialize the DI container."""
        self._registrations: Dict[Type, ServiceRegistration] = {}
        self._named_registrations: Dict[str, ServiceRegistration] = {}
        self._factories: Dict[Type, Callable] = {}
        self._singletons: Dict[Type, Any] = {}
        self._scopes: List[ServiceScope] = []
        self._initialization_order: List[Type] = []
        self._disposal_handlers: Dict[Type, Callable] = {}
        self._lock = threading.RLock()
        self._resolving: Set[Type] = set()
        self._current_scope: Optional[ServiceScope] = None
        
    def register(
        self,
        service_type: type,
        implementation: type,
        singleton: bool = True
    ) -> None:
        """
        Register a service implementation (IServiceContainer interface method).
        
        Args:
            service_type: The interface/base type to register
            implementation: The concrete implementation class
            singleton: Whether to use singleton lifetime (default True)
            
        Examples:
            # Register interface with implementation
            container.register(ILogger, ConsoleLogger)
            
            # Register as transient (new instance each time)
            container.register(IService, ServiceImpl, singleton=False)
        """
        lifetime = ServiceLifetime.SINGLETON if singleton else ServiceLifetime.TRANSIENT
        self._register_internal(
            service_type=service_type,
            implementation=implementation,
            lifetime=lifetime
        )
    
    def _register_internal(
        self,
        service_type: Type[T],
        implementation: Optional[Union[Type[T], Callable[..., T]]] = None,
        lifetime: ServiceLifetime = ServiceLifetime.SINGLETON,
        factory: Optional[Callable[['DIContainer'], T]] = None,
        instance: Optional[T] = None,
        dependencies: Optional[Dict[str, Type]] = None
    ) -> None:
        """
        Internal registration method with full flexibility.
        
        Args:
            service_type: The interface/base type to register
            implementation: The concrete implementation (class or factory)
            lifetime: Service lifetime (singleton/transient)
            factory: Optional factory function
            instance: Pre-created instance (for singleton)
            dependencies: Explicit dependency mapping for constructor params
        """
        with self._lock:
            registration = ServiceRegistration(
                service_type=service_type,
                implementation=implementation,
                factory=factory,
                instance=instance,
                lifetime=lifetime,
                dependencies=dependencies
            )
            self._registrations[service_type] = registration
            
            # If instance provided, store as singleton
            if instance is not None:
                self._singletons[service_type] = instance
    
    def register_instance(self, service_type: type, instance: Any) -> None:
        """
        Register a service instance (IServiceContainer interface method).
        
        Args:
            service_type: The interface/base type to register
            instance: Pre-created instance to register as singleton
            
        Examples:
            # Register a pre-created instance
            config = Config()
            container.register_instance(IConfig, config)
        """
        self._register_internal(
            service_type=service_type,
            instance=instance,
            lifetime=ServiceLifetime.SINGLETON
        )
    
    def resolve_all(self, service_type: type) -> List[Any]:
        """
        Resolve all implementations of a service type (IServiceContainer interface method).
        
        Args:
            service_type: The type to resolve
            
        Returns:
            List of all registered implementations for the service type
            
        Examples:
            # Register multiple implementations
            container.register(IPlugin, PluginA)
            container.register(IPlugin, PluginB)
            
            # Get all implementations
            plugins = container.resolve_all(IPlugin)
        """
        with self._lock:
            # For now, return a list with the single registered implementation
            # In the future, we could support multiple registrations for the same type
            if service_type in self._registrations:
                return [self._resolve_internal(service_type)]
            return []
                
    def register_singleton(
        self,
        interface: Type[T],
        implementation: Optional[Union[Type[T], T]] = None,
        instance: Optional[T] = None,
        name: Optional[str] = None,
        dispose_handler: Optional[Callable[[T], None]] = None
    ) -> None:
        """
        Register a singleton service.
        
        Args:
            interface: The interface/base type to register
            implementation: The concrete implementation class
            instance: Pre-created instance (alternative to implementation)
            name: Optional name for named registration
            dispose_handler: Optional handler called when disposing service
        
        Examples:
            # Register with implementation class
            container.register_singleton(ILogger, ConsoleLogger)
            
            # Register with instance
            container.register_singleton(IConfig, instance=Config())
            
            # Register with disposal handler
            container.register_singleton(
                IDatabase,
                DatabaseConnection,
                dispose_handler=lambda db: db.close()
            )
        """
        # For named registrations, create a unique registration
        if name:
            # Create named registration directly
            registration = ServiceRegistration(
                service_type=interface,
                implementation=implementation,
                instance=instance,
                lifetime=ServiceLifetime.SINGLETON
            )
            self._named_registrations[name] = registration
            
            # Also store the instance if provided
            if instance is not None:
                # Store with a composite key for retrieval
                named_key = (interface, name)
                if not hasattr(self, '_named_singletons'):
                    self._named_singletons = {}
                self._named_singletons[named_key] = instance
        else:
            # Normal registration without name
            if instance is not None:
                self._register_internal(interface, instance=instance)
            elif implementation is not None and not inspect.isclass(implementation):
                # It's an instance passed as implementation (backward compatibility)
                self._register_internal(interface, instance=implementation)
            else:
                self._register_internal(interface, implementation, lifetime=ServiceLifetime.SINGLETON)
                
        # Handle disposal handler
        if dispose_handler:
            if name:
                # Store with composite key for named services
                if not hasattr(self, '_named_disposal_handlers'):
                    self._named_disposal_handlers = {}
                self._named_disposal_handlers[(interface, name)] = dispose_handler
            else:
                self._disposal_handlers[interface] = dispose_handler
            
        # Track initialization order for proper disposal
        key = (interface, name) if name else interface
        if key not in self._initialization_order:
            self._initialization_order.append(key)
            
    def register_scoped(
        self,
        interface: Type[T],
        implementation: Optional[Type[T]] = None,
        name: Optional[str] = None
    ) -> None:
        """
        Register a scoped service.
        
        Scoped services are created once per scope and shared within that scope.
        
        Args:
            interface: The interface/base type to register
            implementation: The concrete implementation
            name: Optional name for named registration
            
        Examples:
            # Register scoped service
            container.register_scoped(IRequestContext, RequestContext)
            
            # Use in scope
            with container.create_scope() as scope:
                context = container.get(IRequestContext)  # Created
                context2 = container.get(IRequestContext)  # Same instance
        """
        self._register_internal(interface, implementation, lifetime=ServiceLifetime.SCOPED)
        if name:
            self._named_registrations[name] = self._registrations[interface]
    
    def register_transient(
        self,
        service_type: Type[T],
        implementation: Optional[Type[T]] = None
    ) -> None:
        """
        Register a transient service.
        
        Convenience method for registering transient services.
        """
        self._register_internal(service_type, implementation, lifetime=ServiceLifetime.TRANSIENT)
        
    def register_factory(
        self,
        interface: Type[T],
        factory: Callable[['DIContainer'], T],
        lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT,
        name: Optional[str] = None
    ) -> None:
        """
        Register a service with a factory function.
        
        The factory receives the container as parameter for resolving dependencies.
        
        Args:
            interface: The interface/base type to register
            factory: Factory function that creates instances
            lifetime: Service lifetime (default: TRANSIENT for factories)
            name: Optional name for named registration
            
        Examples:
            # Register with factory
            container.register_factory(
                IService,
                lambda c: Service(c.get(ILogger), c.get(IConfig)),
                lifetime=ServiceLifetime.SINGLETON
            )
        """
        self._register_internal(interface, factory=factory, lifetime=lifetime)
        self._factories[interface] = factory
        if name:
            self._named_registrations[name] = self._registrations[interface]
        
    def get(self, interface: Type[T], name: Optional[str] = None) -> T:
        """
        Get service instance with dependency resolution.
        
        This is the primary method for retrieving services from the container.
        It handles all lifetime management and dependency resolution.
        
        Args:
            interface: The type to resolve
            name: Optional name for named registration lookup
            
        Returns:
            Instance of the requested service
            
        Raises:
            ServiceNotFoundError: If service is not registered
            CircularDependencyError: If circular dependencies detected
            
        Examples:
            # Get by interface
            logger = container.get(ILogger)
            
            # Get named service
            primary_db = container.get(IDatabase, name="primary")
        """
        if name:
            if name not in self._named_registrations:
                suggestions = self._get_similar_names(name)
                raise ServiceNotFoundError(
                    f"Named service '{name}' is not registered. "
                    f"Did you mean: {', '.join(suggestions)}?" if suggestions else ""
                )
            
            # Check if we have a pre-stored instance for this named service
            named_key = (interface, name)
            if hasattr(self, '_named_singletons') and named_key in self._named_singletons:
                return self._named_singletons[named_key]
                
            # Otherwise resolve from named registration
            registration = self._named_registrations[name]
            if registration.instance is not None:
                return registration.instance
            elif registration.factory:
                return registration.factory(self)
            else:
                return self.create_instance(registration.implementation, registration.dependencies)
                    
        return self._resolve_internal(interface)
        
    def resolve(self, service_type: Type[T]) -> T:
        """
        Resolve a service from the container (legacy method).
        
        This method is maintained for backward compatibility.
        New code should use get() instead.
        
        Args:
            service_type: The type to resolve
            
        Returns:
            Instance of the requested service
            
        Raises:
            ServiceNotFoundError: If service is not registered
            CircularDependencyError: If circular dependencies detected
        """
        return self.get(service_type)
        
    def _resolve_internal(self, service_type: Type[T]) -> T:
        """
        Internal method to resolve a service.
        
        Handles the actual resolution logic with proper locking and lifecycle management.
        """
        with self._lock:
            # Check for circular dependencies
            if service_type in self._resolving:
                cycle = " -> ".join(str(t.__name__) for t in self._resolving) + f" -> {service_type.__name__}"
                raise CircularDependencyError(f"Circular dependency detected: {cycle}")
                
            # Check if registered
            if service_type not in self._registrations:
                suggestions = self._get_similar_types(service_type)
                error_msg = f"Service {service_type.__name__} is not registered."
                if suggestions:
                    error_msg += f" Did you mean: {', '.join(suggestions)}?"
                raise ServiceNotFoundError(error_msg)
                
            registration = self._registrations[service_type]
            
            # Handle different lifetimes
            if registration.lifetime == ServiceLifetime.SINGLETON:
                # Return existing singleton if available
                if service_type in self._singletons:
                    return self._singletons[service_type]
                    
            elif registration.lifetime == ServiceLifetime.SCOPED:
                # Check current scope
                if self._current_scope:
                    instance = self._current_scope.get_scoped_instance(service_type)
                    if instance is not None:
                        return instance
                        
            # Mark as resolving
            self._resolving.add(service_type)
            
            try:
                # Create instance
                instance = registration.create_instance(self)
                
                # Store based on lifetime
                if registration.lifetime == ServiceLifetime.SINGLETON:
                    self._singletons[service_type] = instance
                    if service_type not in self._initialization_order:
                        self._initialization_order.append(service_type)
                        
                elif registration.lifetime == ServiceLifetime.SCOPED:
                    if self._current_scope:
                        self._current_scope.set_scoped_instance(service_type, instance)
                        
                # Call initialization hook if available
                if hasattr(instance, 'initialize'):
                    try:
                        instance.initialize()
                    except Exception as e:
                        logger.error(f"Failed to initialize service {service_type.__name__}: {e}")
                        raise
                        
                return instance
                
            finally:
                self._resolving.remove(service_type)
                
    def resolve_optional(self, service_type: Type[T], default: Optional[T] = None) -> Optional[T]:
        """
        Resolve a service if registered, otherwise return default.
        
        Useful for optional dependencies.
        """
        try:
            return self.resolve(service_type)
        except ServiceNotFoundError:
            return default
            
    def create_instance(self, cls: Type[T], explicit_deps: Optional[Dict[str, Type]] = None) -> T:
        """
        Create an instance of a class, resolving constructor dependencies.
        
        Args:
            cls: The class to instantiate
            explicit_deps: Explicit dependency mapping for constructor params
            
        Returns:
            New instance with resolved dependencies
        """
        # Get constructor signature
        sig = inspect.signature(cls.__init__)
        kwargs = {}
        
        for param_name, param in sig.parameters.items():
            if param_name == 'self':
                continue
                
            # Check explicit dependencies first
            if explicit_deps and param_name in explicit_deps:
                dep_type = explicit_deps[param_name]
                kwargs[param_name] = self.resolve(dep_type)
                continue
                
            # Try to resolve by type annotation
            if param.annotation != param.empty:
                param_type = param.annotation
                
                # Handle string annotations (forward references)
                if isinstance(param_type, str):
                    # Try to resolve forward reference
                    try:
                        # Get the module where the class is defined
                        import sys
                        frame = sys._getframe(1)
                        module = frame.f_globals
                        if param_type in module:
                            param_type = module[param_type]
                        else:
                            # Try looking in registrations by name
                            for reg_type in self._registrations:
                                if reg_type.__name__ == param_type:
                                    param_type = reg_type
                                    break
                    except:
                        # If we can't resolve, skip this parameter
                        if param.default != param.empty:
                            kwargs[param_name] = param.default
                        continue
                
                # Handle Optional types
                if hasattr(param_type, '__origin__') and param_type.__origin__ is Union:
                    # Get the non-None type from Optional
                    args = param_type.__args__
                    param_type = next((arg for arg in args if arg is not type(None)), None)
                    
                if param_type and param_type in self._registrations:
                    # Check for circular dependency
                    if param_type in self._resolving:
                        # Circular dependency detected
                        cycle = " -> ".join(str(t.__name__) for t in self._resolving) + f" -> {param_type.__name__}"
                        raise CircularDependencyError(f"Circular dependency detected: {cycle}")
                    else:
                        kwargs[param_name] = self.resolve(param_type)
                elif param.default != param.empty:
                    # Use default value
                    kwargs[param_name] = param.default
                    
        return cls(**kwargs)
        
    def is_registered(self, service_type: Type) -> bool:
        """Check if a service type is registered."""
        return service_type in self._registrations
        
    def get_all_registrations(self) -> Dict[Type, ServiceRegistration]:
        """Get all service registrations."""
        with self._lock:
            return self._registrations.copy()
            
    def create_scope(self) -> ServiceScope:
        """
        Create a new service scope.
        
        Scoped services will be created once per scope and shared within that scope.
        
        Returns:
            New ServiceScope instance
            
        Examples:
            # Use scope with context manager
            with container.create_scope() as scope:
                # Services with SCOPED lifetime are shared within this scope
                service1 = container.get(IScopedService)
                service2 = container.get(IScopedService)
                assert service1 is service2  # Same instance
                
            # Scope is disposed, scoped instances are cleaned up
        """
        scope = ServiceScope(self)
        with self._lock:
            self._scopes.append(scope)
            # Set as current scope for resolution
            old_scope = self._current_scope
            self._current_scope = scope
            
        # Return a context manager that restores old scope
        class ScopeContext:
            def __init__(self, container, new_scope, old_scope):
                self.container = container
                self.new_scope = new_scope
                self.old_scope = old_scope
                
            def __enter__(self):
                return self.new_scope
                
            def __exit__(self, exc_type, exc_val, exc_tb):
                with self.container._lock:
                    self.container._current_scope = self.old_scope
                    if self.new_scope in self.container._scopes:
                        self.container._scopes.remove(self.new_scope)
                self.new_scope.dispose()
                
        return ScopeContext(self, scope, old_scope)
        
    def create_child_container(self) -> 'DIContainer':
        """
        Create a child container that inherits registrations.
        
        Useful for isolated scenarios where you want separate singleton instances.
        """
        child = DIContainer()
        with self._lock:
            # Copy registrations but not singleton instances
            for service_type, registration in self._registrations.items():
                child._registrations[service_type] = registration
            # Copy named registrations
            child._named_registrations = self._named_registrations.copy()
            # Copy factories
            child._factories = self._factories.copy()
            # Copy disposal handlers
            child._disposal_handlers = self._disposal_handlers.copy()
        return child
        
    def dispose(self) -> None:
        """
        Dispose of the container and all managed services.
        
        Calls disposal handlers for singleton services in reverse initialization order.
        Also disposes any active scopes.
        """
        with self._lock:
            # Dispose scopes first
            for scope in reversed(self._scopes):
                scope.dispose()
            self._scopes.clear()
            
            # Dispose singletons in reverse initialization order
            for service_type in reversed(self._initialization_order):
                if service_type in self._singletons:
                    instance = self._singletons[service_type]
                    
                    # Call disposal handler if registered
                    if service_type in self._disposal_handlers:
                        try:
                            self._disposal_handlers[service_type](instance)
                        except Exception as e:
                            logger.error(f"Error in disposal handler for {service_type.__name__}: {e}")
                            
                    # Call dispose method if available
                    elif hasattr(instance, 'dispose'):
                        try:
                            instance.dispose()
                        except Exception as e:
                            logger.error(f"Error disposing service {service_type.__name__}: {e}")
                            
            # Clear everything
            self._singletons.clear()
            self._initialization_order.clear()
            self._current_scope = None
            
    def clear(self) -> None:
        """Clear all registrations and instances."""
        self.dispose()
        with self._lock:
            self._registrations.clear()
            self._named_registrations.clear()
            self._factories.clear()
            self._disposal_handlers.clear()
            self._resolving.clear()
            
    def _get_similar_types(self, service_type: Type) -> List[str]:
        """
        Get similar registered type names for better error messages.
        
        Uses simple string similarity to suggest possible alternatives.
        """
        if not self._registrations:
            return []
            
        type_name = service_type.__name__.lower()
        similar = []
        
        for registered_type in self._registrations.keys():
            registered_name = registered_type.__name__
            registered_lower = registered_name.lower()
            
            # Check for substring match
            if type_name in registered_lower or registered_lower in type_name:
                similar.append(registered_name)
                continue
                
            # Check for common prefix
            common_prefix_len = 0
            for i, (a, b) in enumerate(zip(type_name, registered_lower)):
                if a == b:
                    common_prefix_len = i + 1
                else:
                    break
                    
            if common_prefix_len >= min(3, len(type_name) // 2):
                similar.append(registered_name)
                
        return similar[:3]  # Return top 3 suggestions
        
    def _get_similar_names(self, name: str) -> List[str]:
        """
        Get similar registered names for better error messages.
        """
        if not self._named_registrations:
            return []
            
        name_lower = name.lower()
        similar = []
        
        for registered_name in self._named_registrations.keys():
            registered_lower = registered_name.lower()
            
            # Check for substring match
            if name_lower in registered_lower or registered_lower in name_lower:
                similar.append(registered_name)
                continue
                
            # Check for common prefix
            common_prefix_len = 0
            for i, (a, b) in enumerate(zip(name_lower, registered_lower)):
                if a == b:
                    common_prefix_len = i + 1
                else:
                    break
                    
            if common_prefix_len >= min(3, len(name_lower) // 2):
                similar.append(registered_name)
                
        return similar[:3]  # Return top 3 suggestions


# Global container instance (optional, for convenience)
_global_container: Optional[DIContainer] = None
_global_lock = threading.Lock()


def get_container() -> DIContainer:
    """
    Get the global DI container instance.
    
    Creates one if it doesn't exist.
    """
    global _global_container
    with _global_lock:
        if _global_container is None:
            _global_container = DIContainer()
        return _global_container


def set_container(container: DIContainer) -> None:
    """Set the global DI container instance."""
    global _global_container
    with _global_lock:
        _global_container = container