"""
Shared singleton management utilities to reduce duplication.
"""

import threading
from typing import Any, TypeVar

from ..logger import get_logger

T = TypeVar("T")


class SingletonManager:
    """
    Centralized singleton management utility.

    Reduces duplication by providing thread-safe singleton patterns
    that can be used across different classes.

    Uses RLock (reentrant locks) to support recursive calls from
    SingletonMixin.__new__ and @singleton decorator patterns.
    """

    _instances: dict[type, Any] = {}
    _locks: dict[type, threading.RLock] = {}
    _global_lock = threading.RLock()

    @classmethod
    def get_instance(
        cls, singleton_class: type[T], *args, force_new: bool = False, **kwargs
    ) -> T:
        """
        Get singleton instance of a class.

        Args:
            singleton_class: Class to get singleton instance of
            *args: Arguments for class constructor
            force_new: Force creation of new instance
            **kwargs: Keyword arguments for class constructor

        Returns:
            Singleton instance
        """
        # Get or create lock for this class
        if singleton_class not in cls._locks:
            with cls._global_lock:
                if singleton_class not in cls._locks:
                    cls._locks[singleton_class] = threading.RLock()

        # Get instance with class-specific lock
        with cls._locks[singleton_class]:
            if force_new or singleton_class not in cls._instances:
                logger = get_logger("singleton_manager")
                logger.debug(f"Creating singleton instance: {singleton_class.__name__}")

                # Use object.__new__ to bypass SingletonMixin.__new__ and avoid recursion
                instance = object.__new__(singleton_class)
                cls._instances[singleton_class] = instance

                # Now call __init__ explicitly with the stored instance
                instance.__init__(*args, **kwargs)

                return instance

            return cls._instances[singleton_class]

    @classmethod
    def has_instance(cls, singleton_class: type) -> bool:
        """
        Check if singleton instance exists.

        Args:
            singleton_class: Class to check

        Returns:
            True if instance exists
        """
        return singleton_class in cls._instances

    @classmethod
    def clear_instance(cls, singleton_class: type) -> None:
        """
        Clear singleton instance.

        Args:
            singleton_class: Class to clear instance for
        """
        if singleton_class in cls._locks:
            with cls._locks[singleton_class]:
                if singleton_class in cls._instances:
                    logger = get_logger("singleton_manager")
                    logger.debug(
                        f"Clearing singleton instance: {singleton_class.__name__}"
                    )
                    del cls._instances[singleton_class]

    @classmethod
    def clear_all_instances(cls) -> None:
        """Clear all singleton instances."""
        with cls._global_lock:
            logger = get_logger("singleton_manager")
            logger.debug(f"Clearing {len(cls._instances)} singleton instances")
            cls._instances.clear()

    @classmethod
    def get_instance_info(cls) -> dict[str, Any]:
        """
        Get information about managed instances.

        Returns:
            Dictionary with instance information
        """
        return {
            "instance_count": len(cls._instances),
            "instance_types": [cls_type.__name__ for cls_type in cls._instances],
            "lock_count": len(cls._locks),
        }


class SingletonMixin:
    """
    Mixin class to add singleton behavior to any class.

    Usage:
        class MyService(SingletonMixin):
            def __init__(self):
                super().__init__()
                # Your initialization code
    """

    def __new__(cls, *args, **kwargs):
        """Override __new__ to implement singleton pattern."""
        return SingletonManager.get_instance(cls, *args, **kwargs)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        """Get singleton instance."""
        return SingletonManager.get_instance(cls, *args, **kwargs)

    @classmethod
    def clear_instance(cls):
        """Clear singleton instance."""
        SingletonManager.clear_instance(cls)

    @classmethod
    def has_instance(cls) -> bool:
        """Check if instance exists."""
        return SingletonManager.has_instance(cls)


def singleton[T](cls: type[T]) -> type[T]:
    """
    Decorator to make a class a singleton.

    Usage:
        @singleton
        class MyService:
            def __init__(self):
                # Your initialization code
                pass
    """

    def new_new(cls_inner, *args, **kwargs):
        """Replace __new__ to route construction through SingletonManager.

        WHY: The @singleton decorator needs to intercept object creation on the decorated
        class so every ``ClassName(...)`` call returns the same instance.
        WHAT: Delegates to SingletonManager.get_instance with the calling class and the
        original construction arguments; first call creates the instance, subsequent calls
        return the cached one.
        TEST: Apply @singleton to a class; construct two instances; assert both are the
        same object and __init__ was only called once.
        """
        return SingletonManager.get_instance(cls_inner, *args, **kwargs)

    cls.__new__ = new_new

    # Add convenience methods
    cls.get_instance = classmethod(SingletonManager.get_instance)
    cls.clear_instance = classmethod(SingletonManager.clear_instance)
    cls.has_instance = classmethod(SingletonManager.has_instance)

    return cls


# Example usage patterns
if __name__ == "__main__":
    # Example 1: Using SingletonMixin
    class ConfigService(SingletonMixin):
        def __init__(self, config_path: str = "default.yaml"):
            self.config_path = config_path
            self.loaded = True

    # Example 2: Using @singleton decorator
    @singleton
    class LoggerService:
        def __init__(self, log_level: str = "INFO"):
            self.log_level = log_level
            self.initialized = True

    # Example 3: Using SingletonManager directly
    class DatabaseService:
        def __init__(self, connection_string: str):
            self.connection_string = connection_string
            self.connected = True

    # Test the patterns
    config1 = ConfigService("config1.yaml")
    config2 = ConfigService("config2.yaml")  # Same instance as config1
    assert config1 is config2  # nosec B101 - Example code only
    assert config1.config_path == "config1.yaml"  # nosec B101 - Example code only

    logger1 = LoggerService("DEBUG")
    logger2 = LoggerService("ERROR")  # Same instance as logger1
    assert logger1 is logger2  # nosec B101 - Example code only
    assert logger1.log_level == "DEBUG"  # nosec B101 - Example code only

    db1 = SingletonManager.get_instance(DatabaseService, "postgres://localhost")
    db2 = SingletonManager.get_instance(
        DatabaseService, "mysql://localhost"
    )  # Same instance
    assert db1 is db2  # nosec B101 - Example code only
    assert (  # nosec B101 - Example code only
        db1.connection_string == "postgres://localhost"
    )  # First initialization wins
