"""Serverless optimization services for ACB.

Provides cold start optimization, lazy initialization, and resource cleanup
specifically designed for serverless environments. Enhanced with cloud-native
optimization patterns for adaptive connection pooling, tiered caching, and
memory-efficient processing.
"""

import asyncio
import time
import typing as t
from contextlib import asynccontextmanager, suppress
from dataclasses import dataclass, field
from functools import wraps
from weakref import WeakSet

from pydantic import Field
from acb.cleanup import CleanupMixin
from acb.config import Config
from acb.depends import depends
from acb.services._base import ServiceBase, ServiceConfig, ServiceSettings

# Service metadata for discovery system
SERVERLESS_OPTIMIZER_METADATA: t.Any = None

try:
    from acb.services.discovery import (
        ServiceCapability,
        ServiceMetadata,
        ServiceStatus,
        generate_service_id,
    )

    SERVERLESS_OPTIMIZER_METADATA = ServiceMetadata(
        service_id=generate_service_id(),
        name="Serverless Optimizer",
        category="performance",
        service_type="serverless_optimizer",
        version="1.0.0",
        acb_min_version="0.19.1",
        author="ACB Framework Team",
        created_date="2024-01-01T00:00:00",
        last_modified="2024-01-01T00:00:00",
        status=ServiceStatus.STABLE,
        capabilities=[
            ServiceCapability.OPTIMIZATION,
            ServiceCapability.COLD_START_OPTIMIZATION,
            ServiceCapability.LAZY_LOADING,
            ServiceCapability.RESOURCE_MANAGEMENT,
        ],
        description="Cold start optimization and resource management for serverless environments",
        settings_class="ServerlessOptimizerSettings",
        config_example={
            "cold_start_optimization": True,
            "lazy_loading_enabled": True,
            "adapter_preloading": True,
            "resource_cleanup_interval": 300,
        },
    )
except ImportError:
    # Discovery system not available
    SERVERLESS_OPTIMIZER_METADATA = None


@dataclass
class ColdStartMetrics:
    """Metrics for cold start optimization."""

    cold_starts_count: int = 0
    warm_starts_count: int = 0
    average_cold_start_time_ms: float = 0.0
    average_warm_start_time_ms: float = 0.0
    resources_preloaded: int = 0
    memory_usage_mb: float = 0.0
    optimizations_applied: dict[str, int] = field(default_factory=dict)


class ServerlessOptimizerSettings(ServiceSettings):
    """Settings for serverless optimization."""

    # Cold start optimization
    cold_start_optimization: bool = True
    preload_critical_adapters: bool = True
    critical_adapters: list[str] = Field(default_factory=lambda: ["cache", "sql"])

    # Lazy initialization
    lazy_loading_enabled: bool = True
    lazy_loading_threshold_ms: float = 100.0

    # Resource management
    resource_cleanup_enabled: bool = True
    resource_cleanup_interval: float = 300.0  # 5 minutes
    max_idle_time: float = 600.0  # 10 minutes

    # Memory optimization
    memory_monitoring: bool = True
    memory_cleanup_threshold_mb: float = 512.0

    @depends.inject
    def __init__(self, config: Config = depends(), **values: t.Any) -> None:
        super().__init__(**values)


class ServerlessOptimizer(ServiceBase):
    """Serverless optimization service for cold start and resource management."""

    def __init__(
        self,
        service_config: ServiceConfig | None = None,
        settings: ServerlessOptimizerSettings | None = None,
    ) -> None:
        if service_config is None:
            service_config = ServiceConfig(
                service_id="serverless_optimizer",
                name="Serverless Optimizer",
                description="Cold start optimization and resource management",
                priority=10,  # Very high priority for early initialization
            )

        super().__init__(service_config, settings or ServerlessOptimizerSettings())
        self._settings: ServerlessOptimizerSettings = self._settings  # type: ignore
        self._cold_start_metrics = ColdStartMetrics()
        self._preloaded_adapters: dict[str, t.Any] = {}
        self._resource_cleanup_task: asyncio.Task[t.Any] | None = None
        self._startup_time = time.perf_counter()

    async def _initialize(self) -> None:
        """Initialize serverless optimizer."""
        if self._settings.cold_start_optimization:
            await self._perform_cold_start_optimizations()

        if self._settings.resource_cleanup_enabled:
            self._resource_cleanup_task = asyncio.create_task(
                self._resource_cleanup_loop(),
            )

        self.logger.info("Serverless optimizer initialized")

    async def _shutdown(self) -> None:
        """Shutdown serverless optimizer."""
        if self._resource_cleanup_task:
            self._resource_cleanup_task.cancel()
            with suppress(asyncio.CancelledError):
                await self._resource_cleanup_task

    async def _health_check(self) -> dict[str, t.Any]:
        """Health check for serverless optimizer."""
        return {
            "cold_starts": self._cold_start_metrics.cold_starts_count,
            "warm_starts": self._cold_start_metrics.warm_starts_count,
            "preloaded_adapters": len(self._preloaded_adapters),
            "average_cold_start_ms": self._cold_start_metrics.average_cold_start_time_ms,
            "memory_usage_mb": self._cold_start_metrics.memory_usage_mb,
        }

    async def _perform_cold_start_optimizations(self) -> None:
        """Perform cold start optimizations."""
        start_time = time.perf_counter()

        try:
            # Preload critical adapters
            if self._settings.preload_critical_adapters:
                await self._preload_adapters()

            # Setup lazy loading patterns
            if self._settings.lazy_loading_enabled:
                self._setup_lazy_loading()

            # Record cold start metrics
            duration = (time.perf_counter() - start_time) * 1000
            self._cold_start_metrics.cold_starts_count += 1
            self._update_cold_start_average(duration)

            self.logger.info(f"Cold start optimization completed in {duration:.2f}ms")

        except Exception as e:
            self.logger.exception(f"Cold start optimization failed: {e}")
            self.record_error(str(e))

    async def _preload_adapters(self) -> None:
        """Preload critical adapters for faster warm starts."""
        from acb.adapters import import_adapter

        for adapter_name in self._settings.critical_adapters:
            try:
                start_time = time.perf_counter()
                adapter_class = import_adapter(adapter_name)
                adapter_instance = await depends.get(adapter_class)

                # Initialize adapter if it has async initialization
                if hasattr(adapter_instance, "initialize"):
                    await adapter_instance.initialize()

                self._preloaded_adapters[adapter_name] = adapter_instance
                duration = (time.perf_counter() - start_time) * 1000

                self.logger.debug(
                    f"Preloaded {adapter_name} adapter in {duration:.2f}ms",
                )
                self._cold_start_metrics.resources_preloaded += 1

            except Exception as e:
                self.logger.warning(f"Failed to preload {adapter_name} adapter: {e}")

    def _setup_lazy_loading(self) -> None:
        """Setup lazy loading patterns for non-critical resources."""
        # This could be enhanced to setup lazy loading for various components
        self.logger.debug("Lazy loading patterns configured")

    def _update_cold_start_average(self, duration: float) -> None:
        """Update cold start time average."""
        count = self._cold_start_metrics.cold_starts_count
        current_avg = self._cold_start_metrics.average_cold_start_time_ms
        self._cold_start_metrics.average_cold_start_time_ms = (
            current_avg * (count - 1) + duration
        ) / count

    async def _resource_cleanup_loop(self) -> None:
        """Background resource cleanup loop."""
        while not self._shutdown_event.is_set():
            try:
                await asyncio.sleep(self._settings.resource_cleanup_interval)
                if self._shutdown_event.is_set():
                    break

                await self._cleanup_idle_resources()

            except asyncio.CancelledError:
                break
            except Exception as e:
                self.logger.exception(f"Resource cleanup error: {e}")
                await asyncio.sleep(60)

    async def _cleanup_idle_resources(self) -> None:
        """Cleanup idle resources to optimize memory usage."""
        try:
            # Memory monitoring
            if self._settings.memory_monitoring:
                await self._monitor_memory_usage()

            # Identify and cleanup idle adapters
            idle_adapters = self._identify_idle_adapters()
            await self._cleanup_adapters(idle_adapters)

        except Exception as e:
            self.logger.exception(f"Error during resource cleanup: {e}")

    def _identify_idle_adapters(self) -> list[str]:
        """Identify adapters that have been idle too long.

        Returns:
            List of idle adapter names
        """
        idle_adapters = []
        current_time = time.perf_counter()

        for name, adapter in self._preloaded_adapters.items():
            if self._is_adapter_idle(adapter, current_time):
                idle_adapters.append(name)

        return idle_adapters

    def _is_adapter_idle(self, adapter: t.Any, current_time: float) -> bool:
        """Check if adapter has been idle too long.

        Args:
            adapter: Adapter instance
            current_time: Current timestamp

        Returns:
            True if adapter is idle
        """
        if not hasattr(adapter, "_last_used_time"):
            return False

        last_used = getattr(adapter, "_last_used_time", current_time)
        return current_time - last_used > self._settings.max_idle_time

    async def _cleanup_adapters(self, adapter_names: list[str]) -> None:
        """Cleanup idle adapters.

        Args:
            adapter_names: List of adapter names to cleanup
        """
        for name in adapter_names:
            adapter = self._preloaded_adapters[name]

            if hasattr(adapter, "cleanup"):
                await adapter.cleanup()

            del self._preloaded_adapters[name]
            self.logger.debug(f"Cleaned up idle adapter: {name}")

    async def _monitor_memory_usage(self) -> None:
        """Monitor and report memory usage."""
        try:
            import psutil

            process = psutil.Process()
            memory_mb = process.memory_info().rss / 1024 / 1024
            self._cold_start_metrics.memory_usage_mb = memory_mb

            if memory_mb > self._settings.memory_cleanup_threshold_mb:
                self.logger.warning(f"High memory usage detected: {memory_mb:.2f}MB")
                # Could trigger more aggressive cleanup here

        except ImportError:
            # psutil not available
            pass
        except Exception as e:
            self.logger.exception(f"Memory monitoring error: {e}")

    def get_metrics(self) -> ColdStartMetrics:
        """Get current cold start metrics."""
        return self._cold_start_metrics


class LazyInitializer(CleanupMixin):
    """Lazy initialization wrapper for expensive resources."""

    def __init__(
        self,
        factory: t.Callable[[], t.Awaitable[t.Any]],
        timeout: float = 30.0,
    ) -> None:
        super().__init__()
        self._factory = factory
        self._timeout = timeout
        self._instance: t.Any = None
        self._initialization_lock = asyncio.Lock()
        self._initialized = False

    async def get(self) -> t.Any:
        """Get the lazily initialized instance."""
        if self._initialized:
            return self._instance

        async with self._initialization_lock:
            if self._initialized:
                return self._instance

            try:
                self._instance = await asyncio.wait_for(
                    self._factory(),
                    timeout=self._timeout,
                )
                self._initialized = True

                # Register for cleanup if the instance supports it
                if hasattr(self._instance, "cleanup"):
                    self.register_resource(self._instance)

                return self._instance

            except TimeoutError:
                msg = f"Lazy initialization timed out after {self._timeout}s"
                raise RuntimeError(
                    msg,
                )

    @property
    def is_initialized(self) -> bool:
        """Check if the resource has been initialized."""
        return self._initialized

    async def reset(self) -> None:
        """Reset the lazy initializer, clearing the cached instance."""
        async with self._initialization_lock:
            if self._instance and hasattr(self._instance, "cleanup"):
                await self._instance.cleanup()
            self._instance = None
            self._initialized = False


class AdapterPreInitializer:
    """Pre-initialization manager for adapters."""

    def __init__(self) -> None:
        self._preinitialized: dict[str, t.Any] = {}
        self._lazy_initializers: dict[str, LazyInitializer] = {}

    async def preinitialize_adapter(
        self,
        adapter_name: str,
        eager: bool = False,
    ) -> None:
        """Pre-initialize an adapter."""
        try:
            from acb.adapters import import_adapter

            if eager:
                # Eager initialization
                adapter_class = import_adapter(adapter_name)
                adapter_instance = await depends.get(adapter_class)

                if hasattr(adapter_instance, "initialize"):
                    await adapter_instance.initialize()

                self._preinitialized[adapter_name] = adapter_instance

            else:
                # Lazy initialization
                async def factory() -> t.Any:
                    adapter_class = import_adapter(adapter_name)
                    adapter_instance = await depends.get(adapter_class)

                    if hasattr(adapter_instance, "initialize"):
                        await adapter_instance.initialize()

                    return adapter_instance

                self._lazy_initializers[adapter_name] = LazyInitializer(factory)

        except Exception as e:
            msg = f"Failed to pre-initialize adapter {adapter_name}: {e}"
            raise RuntimeError(msg)

    async def get_adapter(self, adapter_name: str) -> t.Any:
        """Get a pre-initialized adapter."""
        if adapter_name in self._preinitialized:
            return self._preinitialized[adapter_name]

        if adapter_name in self._lazy_initializers:
            return await self._lazy_initializers[adapter_name].get()

        msg = f"Adapter {adapter_name} not pre-initialized"
        raise ValueError(msg)

    def is_preinitialized(self, adapter_name: str) -> bool:
        """Check if an adapter is pre-initialized."""
        return (
            adapter_name in self._preinitialized
            or adapter_name in self._lazy_initializers
        )


class FastDependencies:
    """Optimized dependency injection resolution."""

    def __init__(self) -> None:
        self._resolution_cache: dict[str, t.Any] = {}
        self._resolution_times: dict[str, float] = {}

    def cached_resolve(
        self,
        dependency_type: type[t.Any],
        cache_key: str | None = None,
    ) -> t.Any:
        """Resolve dependency with caching."""
        key = cache_key or f"{dependency_type.__module__}.{dependency_type.__name__}"

        if key in self._resolution_cache:
            return self._resolution_cache[key]

        start_time = time.perf_counter()
        instance = depends.get_sync(dependency_type)
        resolution_time = (time.perf_counter() - start_time) * 1000

        self._resolution_cache[key] = instance
        self._resolution_times[key] = resolution_time

        return instance

    def clear_cache(self) -> None:
        """Clear the resolution cache."""
        self._resolution_cache.clear()
        self._resolution_times.clear()

    def get_resolution_stats(self) -> dict[str, float]:
        """Get dependency resolution statistics."""
        return self._resolution_times.copy()


class ServerlessResourceCleanup(CleanupMixin):
    """Resource cleanup manager for serverless environments."""

    def __init__(self, cleanup_interval: float = 300.0) -> None:
        super().__init__()
        self._cleanup_interval = cleanup_interval
        self._tracked_resources: WeakSet[t.Any] = WeakSet()
        self._cleanup_task: asyncio.Task[t.Any] | None = None

    def track_resource(self, resource: t.Any) -> None:
        """Track a resource for cleanup."""
        self._tracked_resources.add(resource)

    async def start_cleanup_loop(self) -> None:
        """Start the resource cleanup loop."""
        if self._cleanup_task is None or self._cleanup_task.done():
            self._cleanup_task = asyncio.create_task(self._cleanup_loop())

    async def stop_cleanup_loop(self) -> None:
        """Stop the resource cleanup loop."""
        if self._cleanup_task:
            self._cleanup_task.cancel()
            with suppress(asyncio.CancelledError):
                await self._cleanup_task

    async def _cleanup_loop(self) -> None:
        """Background cleanup loop."""
        while True:
            try:
                await asyncio.sleep(self._cleanup_interval)
                await self._perform_cleanup()

            except asyncio.CancelledError:
                break
            except Exception as e:
                # Log error but continue cleanup loop
                import logging

                logging.getLogger(__name__).exception(f"Cleanup loop error: {e}")

    async def _perform_cleanup(self) -> None:
        """Perform resource cleanup."""
        cleanup_count = 0

        # Use list() to avoid set changing during iteration
        for resource in list(self._tracked_resources):
            try:
                if hasattr(resource, "cleanup"):
                    await resource.cleanup()
                    cleanup_count += 1

            except Exception as e:
                import logging

                logging.getLogger(__name__).exception(
                    f"Error cleaning up resource: {e}"
                )

        if cleanup_count > 0:
            import logging

            logging.getLogger(__name__).info(f"Cleaned up {cleanup_count} resources")


# Convenience functions for optimization


def optimize_cold_start(
    preload_adapters: list[str] | None = None,
) -> t.Callable[
    [t.Callable[..., t.Awaitable[t.Any]]],
    t.Callable[..., t.Awaitable[t.Any]],
]:
    """Decorator to optimize function cold starts."""

    def decorator(
        func: t.Callable[..., t.Awaitable[t.Any]],
    ) -> t.Callable[..., t.Awaitable[t.Any]]:
        @wraps(func)
        async def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
            # Record start as warm start if already optimized
            start_time = time.perf_counter()

            try:
                result = await func(*args, **kwargs)
                duration_ms = (time.perf_counter() - start_time) * 1000

                # Could track warm start metrics here
                # self._track_warm_start(duration_ms) if needed
                _ = duration_ms  # Placeholder for future metrics tracking
                return result

            except Exception:
                duration_ms = (time.perf_counter() - start_time) * 1000
                # Could track failed execution metrics here
                # self._track_failed_execution(duration_ms) if needed
                _ = duration_ms  # Placeholder for future metrics tracking
                raise

        return wrapper

    return decorator


@asynccontextmanager
async def lazy_resource(
    factory: t.Callable[[], t.Awaitable[t.Any]],
    timeout: float = 30.0,
) -> t.AsyncGenerator[t.Any]:
    """Context manager for lazy resource initialization."""
    initializer = LazyInitializer(factory, timeout)
    try:
        resource = await initializer.get()
        yield resource
    finally:
        await initializer.cleanup()


# Cloud-Native Optimization Components


class AdaptiveConnectionPool:
    """Adaptive connection pool for serverless environments.

    Automatically scales connection pool sizes based on usage patterns
    and serverless constraints.
    """

    def __init__(
        self,
        min_connections: int = 1,
        max_connections: int = 10,
        scale_factor: float = 1.5,
        idle_timeout: float = 300.0,
    ) -> None:
        self._min_connections = min_connections
        self._max_connections = max_connections
        self._scale_factor = scale_factor
        self._idle_timeout = idle_timeout

        self._pool: set[t.Any] = set()
        self._active_connections: set[t.Any] = set()
        self._usage_stats: dict[str, float] = {}
        self._last_scale_time = time.time()
        self._creation_lock = asyncio.Lock()

    async def acquire(
        self,
        connection_factory: t.Callable[[], t.Awaitable[t.Any]],
    ) -> t.Any:
        """Acquire a connection from the pool."""
        # Try to get an existing connection
        if self._pool:
            connection = self._pool.pop()
            self._active_connections.add(connection)
            return connection

        # Create new connection if under max limit
        if len(self._active_connections) < self._max_connections:
            async with self._creation_lock:
                connection = await connection_factory()
                self._active_connections.add(connection)
                await self._record_usage("connection_created")
                return connection

        # Wait for a connection to become available
        while not self._pool:
            await asyncio.sleep(0.1)

        connection = self._pool.pop()
        self._active_connections.add(connection)
        return connection

    async def release(self, connection: t.Any) -> None:
        """Release a connection back to the pool."""
        if connection in self._active_connections:
            self._active_connections.remove(connection)

            # Add back to pool if it's healthy and we need it
            if await self._is_connection_healthy(connection):
                self._pool.add(connection)
                await self._record_usage("connection_released")
            else:
                await self._close_connection(connection)

    async def scale_down_if_needed(self) -> None:
        """Scale down pool if connections are idle."""
        current_time = time.time()
        if current_time - self._last_scale_time < 60:  # Don't scale too frequently
            return

        total_connections = len(self._pool) + len(self._active_connections)
        if total_connections > self._min_connections and not self._active_connections:
            # Close idle connections
            connections_to_close = list(self._pool)[: -self._min_connections]
            for conn in connections_to_close:
                self._pool.remove(conn)
                await self._close_connection(conn)

            self._last_scale_time = current_time

    async def _is_connection_healthy(self, connection: t.Any) -> bool:
        """Check if a connection is still healthy."""
        try:
            # Basic health check - override in subclasses for specific protocols
            return hasattr(connection, "is_closed") and not connection.is_closed
        except Exception:
            return False

    async def _close_connection(self, connection: t.Any) -> None:
        """Close a connection safely."""
        with suppress(Exception):
            if hasattr(connection, "close"):
                await connection.close()
            elif hasattr(connection, "disconnect"):
                await connection.disconnect()

    async def _record_usage(self, event: str) -> None:
        """Record usage statistics."""
        self._usage_stats[event] = time.time()

    def get_pool_stats(self) -> dict[str, t.Any]:
        """Get pool statistics."""
        return {
            "active_connections": len(self._active_connections),
            "pooled_connections": len(self._pool),
            "total_connections": len(self._active_connections) + len(self._pool),
            "max_connections": self._max_connections,
            "usage_stats": self._usage_stats.copy(),
        }


class ServerlessTieredCache:
    """Multi-tier caching system optimized for serverless environments.

    Combines memory, Redis, and object storage for optimal cost/performance.
    """

    def __init__(
        self,
        memory_limit_mb: float = 128.0,
        redis_ttl: int = 3600,
        storage_ttl: int = 86400,
    ) -> None:
        self._memory_limit_mb = memory_limit_mb
        self._redis_ttl = redis_ttl
        self._storage_ttl = storage_ttl

        self._memory_cache: dict[str, tuple[t.Any, float]] = {}
        self._memory_usage_mb = 0.0
        self._cache_stats = {
            "memory_hits": 0,
            "redis_hits": 0,
            "storage_hits": 0,
            "misses": 0,
        }

    async def get(self, key: str) -> t.Any | None:
        """Get value from tiered cache."""
        # Try memory first
        if key in self._memory_cache:
            value, timestamp = self._memory_cache[key]
            if time.time() - timestamp < 300:  # 5 minute memory TTL
                self._cache_stats["memory_hits"] += 1
                return value
            del self._memory_cache[key]

        # Try Redis
        with suppress(Exception):
            # This would integrate with ACB's Redis adapter
            value = await self._get_from_redis(key)
            if value is not None:
                self._cache_stats["redis_hits"] += 1
                await self._store_in_memory(key, value)
                return value

        # Try storage
        with suppress(Exception):
            value = await self._get_from_storage(key)
            if value is not None:
                self._cache_stats["storage_hits"] += 1
                await self._store_in_memory(key, value)
                await self._store_in_redis(key, value)
                return value

        self._cache_stats["misses"] += 1
        return None

    async def set(self, key: str, value: t.Any, ttl: int | None = None) -> None:
        """Set value in tiered cache."""
        # Always store in memory first
        await self._store_in_memory(key, value)

        # Store in Redis with longer TTL
        with suppress(Exception):
            await self._store_in_redis(key, value, ttl or self._redis_ttl)

        # Store in object storage for longest persistence
        if ttl is None or ttl > 3600:  # Only for long-term storage
            with suppress(Exception):
                await self._store_in_storage(key, value)

    async def _store_in_memory(self, key: str, value: t.Any) -> None:
        """Store value in memory cache with size management."""
        # Estimate memory usage (rough approximation)
        estimated_size = len(str(value)) / 1024 / 1024  # Convert to MB

        # Evict if necessary
        while (
            self._memory_usage_mb + estimated_size > self._memory_limit_mb
            and self._memory_cache
        ):
            oldest_key = min(
                self._memory_cache.keys(),
                key=lambda k: self._memory_cache[k][1],
            )
            del self._memory_cache[oldest_key]
            self._memory_usage_mb *= 0.9  # Rough adjustment

        self._memory_cache[key] = (value, time.time())
        self._memory_usage_mb += estimated_size

    async def _get_from_redis(self, key: str) -> t.Any | None:
        """Get value from Redis cache."""
        # Integration point with ACB Redis adapter
        # This would use: Cache = import_adapter("cache")
        return None  # Placeholder

    async def _store_in_redis(self, key: str, value: t.Any, ttl: int = 3600) -> None:
        """Store value in Redis cache."""
        # Integration point with ACB Redis adapter
        # Placeholder

    async def _get_from_storage(self, key: str) -> t.Any | None:
        """Get value from object storage."""
        # Integration point with ACB Storage adapter
        return None  # Placeholder

    async def _store_in_storage(self, key: str, value: t.Any) -> None:
        """Store value in object storage."""
        # Integration point with ACB Storage adapter
        # Placeholder

    def get_cache_stats(self) -> dict[str, t.Any]:
        """Get cache statistics."""
        total_requests = sum(self._cache_stats.values())
        hit_rate = (
            (
                self._cache_stats["memory_hits"]
                + self._cache_stats["redis_hits"]
                + self._cache_stats["storage_hits"]
            )
            / max(total_requests, 1)
        ) * 100

        return self._cache_stats | {
            "hit_rate_percent": hit_rate,
            "memory_usage_mb": self._memory_usage_mb,
            "memory_entries": len(self._memory_cache),
        }


class DeferredInitializer:
    """Deferred initialization for expensive resources in serverless environments.

    Initializes resources only when actually needed, with priority-based loading.
    """

    def __init__(self) -> None:
        self._initializers: dict[
            str,
            tuple[t.Callable[[], t.Awaitable[t.Any]], int],
        ] = {}
        self._initialized: dict[str, t.Any] = {}
        self._initialization_order: list[str] = []
        self._locks: dict[str, asyncio.Lock] = {}

    def register(
        self,
        name: str,
        factory: t.Callable[[], t.Awaitable[t.Any]],
        priority: int = 100,
    ) -> None:
        """Register a deferred initializer."""
        self._initializers[name] = (factory, priority)
        self._locks[name] = asyncio.Lock()

    async def get(self, name: str) -> t.Any:
        """Get or initialize a resource."""
        if name in self._initialized:
            return self._initialized[name]

        if name not in self._initializers:
            msg = f"No initializer registered for '{name}'"
            raise ValueError(msg)

        async with self._locks[name]:
            # Double-check after acquiring lock
            if name in self._initialized:
                return self._initialized[name]

            factory, _ = self._initializers[name]
            resource = await factory()
            self._initialized[name] = resource
            self._initialization_order.append(name)
            return resource

    async def initialize_by_priority(self, max_concurrent: int = 3) -> None:
        """Initialize resources by priority order."""
        # Sort by priority (lower numbers = higher priority)
        sorted_items = sorted(self._initializers.items(), key=lambda x: x[1][1])

        # Initialize in batches to avoid overwhelming the system
        for i in range(0, len(sorted_items), max_concurrent):
            batch = sorted_items[i : i + max_concurrent]
            await asyncio.gather(
                *[self.get(name) for name, _ in batch],
                return_exceptions=True,
            )

    def is_initialized(self, name: str) -> bool:
        """Check if a resource is initialized."""
        return name in self._initialized

    def get_initialization_stats(self) -> dict[str, t.Any]:
        """Get initialization statistics."""
        return {
            "registered_count": len(self._initializers),
            "initialized_count": len(self._initialized),
            "initialization_order": self._initialization_order.copy(),
            "pending": [
                name for name in self._initializers if name not in self._initialized
            ],
        }


class MemoryEfficientProcessor:
    """Memory-efficient processing patterns for edge devices and serverless.

    Implements streaming processing, batch optimization, and memory monitoring.
    """

    def __init__(
        self,
        max_memory_mb: float = 256.0,
        batch_size: int = 100,
        stream_threshold: int = 1000,
    ) -> None:
        self._max_memory_mb = max_memory_mb
        self._batch_size = batch_size
        self._stream_threshold = stream_threshold
        self._current_memory_mb = 0.0
        self._processing_stats = {
            "items_processed": 0,
            "batches_processed": 0,
            "memory_warnings": 0,
            "streams_processed": 0,
        }

    async def process_items(
        self,
        items: t.Iterable[t.Any],
        processor: t.Callable[[list[t.Any]], t.Awaitable[list[t.Any]]],
    ) -> t.AsyncGenerator[t.Any]:
        """Process items efficiently based on memory constraints."""
        from collections.abc import Sized

        items_count = len(items) if isinstance(items, Sized) else None

        # Use streaming for large datasets
        if items_count and items_count > self._stream_threshold:
            async for result in self._stream_process(items, processor):
                yield result
        else:
            # Use batch processing for smaller datasets
            async for result in self._batch_process(items, processor):
                yield result

    async def _batch_process(
        self,
        items: t.Iterable[t.Any],
        processor: t.Callable[[list[t.Any]], t.Awaitable[list[t.Any]]],
    ) -> t.AsyncGenerator[t.Any]:
        """Process items in batches."""
        batch = []

        for item in items:
            batch.append(item)

            if len(batch) >= self._batch_size:
                results = await self._process_batch(batch, processor)
                for result in results:
                    yield result
                batch = []

        # Process remaining items
        if batch:
            results = await self._process_batch(batch, processor)
            for result in results:
                yield result

    async def _stream_process(
        self,
        items: t.Iterable[t.Any],
        processor: t.Callable[[list[t.Any]], t.Awaitable[list[t.Any]]],
    ) -> t.AsyncGenerator[t.Any]:
        """Process items using streaming to minimize memory usage."""
        self._processing_stats["streams_processed"] += 1

        async for item in self._async_iterate(items):
            # Process single items to minimize memory footprint
            results = await processor([item])
            for result in results:
                yield result

            # Check memory periodically
            if self._processing_stats["items_processed"] % 100 == 0:
                await self._check_memory_usage()

    async def _process_batch(
        self,
        batch: list[t.Any],
        processor: t.Callable[[list[t.Any]], t.Awaitable[list[t.Any]]],
    ) -> list[t.Any]:
        """Process a batch of items."""
        await self._check_memory_usage()

        results = await processor(batch)

        self._processing_stats["items_processed"] += len(batch)
        self._processing_stats["batches_processed"] += 1

        return results

    async def _async_iterate(self, items: t.Iterable[t.Any]) -> t.AsyncGenerator[t.Any]:
        """Convert iterable to async generator."""
        for item in items:
            yield item
            # Yield control occasionally for async processing
            if self._processing_stats["items_processed"] % 10 == 0:
                await asyncio.sleep(0)

    async def _check_memory_usage(self) -> None:
        """Monitor memory usage and warn if approaching limits."""
        with suppress(ImportError):
            import psutil

            process = psutil.Process()
            memory_mb = process.memory_info().rss / 1024 / 1024
            self._current_memory_mb = memory_mb

            if memory_mb > self._max_memory_mb * 0.8:  # 80% threshold
                self._processing_stats["memory_warnings"] += 1
                import logging

                logging.getLogger(__name__).warning(
                    f"High memory usage: {memory_mb:.1f}MB / {self._max_memory_mb}MB",
                )

    def get_processing_stats(self) -> dict[str, t.Any]:
        """Get processing statistics."""
        return self._processing_stats | {
            "current_memory_mb": self._current_memory_mb,
            "memory_limit_mb": self._max_memory_mb,
            "memory_utilization_percent": (
                self._current_memory_mb / self._max_memory_mb * 100
                if self._max_memory_mb > 0
                else 0
            ),
        }
