"""Memory-efficient cleanup strategy using IntEnum and class methods.

This module provides a functional-first approach to resource cleanup,
precomputing cleanup strategies at registration time for zero runtime overhead.
Uses IntEnum (4-8 bytes) instead of class instances (56+ bytes) for strategies.
"""

from __future__ import annotations

from enum import IntEnum
from typing import Any, Awaitable, Callable


class CleanupStrategy(IntEnum):
    """Memory-efficient cleanup strategy enumeration.

    Uses IntEnum for minimal memory footprint (4-8 bytes) compared to
    class instances (56+ bytes). Provides class methods for analyzing
    providers and creating cleanup tasks at registration time.

    Values:
        NONE: No cleanup required
        CLOSE: Synchronous close() method
        ACLOSE: Asynchronous aclose() method
        CONTEXT: Synchronous context manager (__exit__)
        ASYNC_CONTEXT: Asynchronous context manager (__aexit__)

    Example:
        >>> strategy = CleanupStrategy.analyze(my_resource)
        >>> if strategy != CleanupStrategy.NONE:
        ...     task = CleanupStrategy.create_task(my_resource, strategy)
        ...     cleanup_queue.append(task)
    """

    NONE = 0
    CLOSE = 1  # sync .close()
    ACLOSE = 2  # async .aclose()
    CONTEXT = 3  # sync __exit__
    ASYNC_CONTEXT = 4  # async __aexit__

    @classmethod
    def analyze(cls, provider: Any) -> CleanupStrategy:
        """Determine cleanup strategy at registration time.

        Analyzes a provider/resource to determine the appropriate cleanup
        strategy. This is a pure function with no side effects that checks
        for cleanup protocols.

        When a resource has both sync and async cleanup methods, the async
        methods take priority. This is by design - if a resource provides
        async cleanup, it's likely designed for async usage. Resources that
        need to work in both contexts should be registered separately or
        use a wrapper that provides the appropriate interface.

        Priority order:
            1. Async context manager (__aenter__/__aexit__)
            2. Sync context manager (__enter__/__exit__)
            3. Async cleanup (aclose)
            4. Sync cleanup (close)
            5. No cleanup needed

        Args:
            provider: The provider/resource to analyze for cleanup capabilities

        Returns:
            CleanupStrategy: The appropriate cleanup strategy enum value

        Example:
            >>> class Database:
            ...     def close(self): pass
            ...     async def aclose(self): pass
            >>> strategy = CleanupStrategy.analyze(Database())
            >>> assert strategy == CleanupStrategy.ACLOSE  # Async takes priority
        """
        # Protocol checking in priority order - async methods take precedence
        # This is intentional: resources with async cleanup are designed for async use
        if hasattr(provider, "__aexit__") and hasattr(provider, "__aenter__"):
            return cls.ASYNC_CONTEXT
        if hasattr(provider, "__exit__") and hasattr(provider, "__enter__"):
            return cls.CONTEXT
        if hasattr(provider, "aclose"):
            return cls.ACLOSE
        if hasattr(provider, "close"):
            return cls.CLOSE
        return cls.NONE

    @classmethod
    def create_task(
        cls, instance: Any, strategy: CleanupStrategy
    ) -> Callable[[], None] | Callable[[], Awaitable[None]]:
        """Create a cleanup task for an instance based on strategy.

        Factory method that creates a cleanup task (callable) for a given
        instance and strategy. The returned lambda captures only the method
        name as a string, not a bound method reference, avoiding holding
        strong references to the instance.

        Args:
            instance: The instance that needs cleanup
            strategy: The cleanup strategy to apply

        Returns:
            Callable: A cleanup task that can be executed later.
                     Returns None for sync cleanup, Awaitable[None] for async.

        Example:
            >>> db = Database()
            >>> task = CleanupStrategy.create_task(db, CleanupStrategy.CLOSE)
            >>> task()  # Executes db.close()

        Note:
            The task uses getattr() to look up the method at execution time,
            which allows the instance to be garbage collected if no other
            references exist.
        """
        match strategy:
            case cls.CLOSE:

                def cleanup_close():
                    """Execute close() cleanup on resource."""
                    return getattr(instance, "close")()

                return cleanup_close

            case cls.ACLOSE:

                def cleanup_aclose():
                    """Execute aclose() async cleanup on resource."""
                    return getattr(instance, "aclose")()

                return cleanup_aclose

            case cls.CONTEXT:

                def cleanup_context_exit():
                    """Execute __exit__ cleanup on context manager."""
                    return instance.__exit__(None, None, None)

                return cleanup_context_exit

            case cls.ASYNC_CONTEXT:

                def cleanup_async_context_exit():
                    """Execute __aexit__ async cleanup on context manager."""
                    return instance.__aexit__(None, None, None)

                return cleanup_async_context_exit

            case _:

                def cleanup_noop():
                    """No-op cleanup for resources without cleanup needs."""
                    return None

                return cleanup_noop
