#!/usr/bin/env python3
"""
System configuration optimizations for improving concurrent performance.
"""

import os
import asyncio
import multiprocessing
import threading
import time
import resource
import logging
from typing import Dict, Any, Optional
from concurrent.futures import ThreadPoolExecutor

# Configure logging
logger = logging.getLogger('app')


class SystemOptimizer:
    """System optimizer.

    Adjusts system parameters to improve concurrent performance.
    """
    
    def __init__(self):
        self.original_settings = {}
        self.optimized = False
    
    def optimize_system_settings(self):
        """Apply optimized system settings."""
        if self.optimized:
            return
        
        # Back up original settings
        self._backup_original_settings()
        
        # 1. Optimize file descriptor limits.
        try:
            soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
            new_limit = min(65536, hard)  # Increase to 65536 or the hard limit
            resource.setrlimit(resource.RLIMIT_NOFILE, (new_limit, hard))
            self.original_settings['RLIMIT_NOFILE'] = (soft, hard)
            logger.info(f"File descriptor limit increased from {soft} to {new_limit}")
        except (ValueError, OSError) as e:
            logger.error(f"Failed to set file descriptor limit: {e}")
        
        # 2. Optimize thread stack size.
        try:
            soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
            new_stack = min(8 * 1024 * 1024, hard)  # 8 MB stack size
            resource.setrlimit(resource.RLIMIT_STACK, (new_stack, hard))
            self.original_settings['RLIMIT_STACK'] = (soft, hard)
            logger.info(f"Thread stack size set to {new_stack // (1024*1024)}MB")
        except (ValueError, OSError) as e:
            logger.error(f"Failed to set thread stack size: {e}")
        
        # 3. Optimize environment variables.
        env_vars = {
            # Python optimizations
            'PYTHONUNBUFFERED': '1',  # Disable output buffering
            'PYTHONDONTWRITEBYTECODE': '1',  # Do not write .pyc files
            
            # Tokenizer optimizations - allow moderate parallelism
            'TOKENIZERS_PARALLELISM': 'true',  # Set to true to improve concurrency
            'TOKENIZERS_FAST': '1',  # Enable fast tokenizer
            
            # OpenMP optimizations
            'OMP_NUM_THREADS': str(min(8, multiprocessing.cpu_count())),  # Limit OpenMP threads
            'OMP_WAIT_POLICY': 'PASSIVE',  # Passive wait policy
            
            # Memory optimizations
            'MALLOC_TRIM_THRESHOLD_': '100000',  # Memory trimming threshold
            
            # Network optimizations
            'TCP_NODELAY': '1',  # Disable Nagle's algorithm
            
            # Hugging Face optimizations
            'TRANSFORMERS_CACHE': '/tmp/transformers_cache',  # Use tmpfs for faster cache access
            'HF_OFFLINE': '0',  # Online mode
            
            # CUDA optimizations if GPU is used
            'CUDA_LAUNCH_BLOCKING': '0',  # Asynchronous CUDA launch
            
            # asyncio optimizations
            'UVLOOP_ENABLED': '1',  # Enable uvloop when available
        }
        
        for key, value in env_vars.items():
            if key not in os.environ:
                os.environ[key] = value
                logger.info(f"Set environment variable: {key}={value}")
        
        self.optimized = True
        logger.info("System optimization completed")
    
    def _backup_original_settings(self):
        """Back up original settings."""
        try:
            self.original_settings['RLIMIT_NOFILE'] = resource.getrlimit(resource.RLIMIT_NOFILE)
            self.original_settings['RLIMIT_STACK'] = resource.getrlimit(resource.RLIMIT_STACK)
        except:
            pass
        
        # Back up important environment variables.
        env_keys = [
            'TOKENIZERS_PARALLELISM',
            'PYTHONUNBUFFERED',
            'PYTHONDONTWRITEBYTECODE',
            'OMP_NUM_THREADS'
        ]
        
        for key in env_keys:
            if key in os.environ:
                self.original_settings[key] = os.environ[key]
    
    def restore_original_settings(self):
        """Restore original settings."""
        if not self.original_settings:
            return
        
        logger.info("Restoring original system settings...")
        
        # Restore resource limits.
        if 'RLIMIT_NOFILE' in self.original_settings:
            try:
                resource.setrlimit(resource.RLIMIT_NOFILE, self.original_settings['RLIMIT_NOFILE'])
                logger.info(f"Restored file descriptor limit")
            except:
                pass
        
        if 'RLIMIT_STACK' in self.original_settings:
            try:
                resource.setrlimit(resource.RLIMIT_STACK, self.original_settings['RLIMIT_STACK'])
                logger.info(f"Restored thread stack size")
            except:
                pass
        
        # Restore environment variables.
        for key, value in self.original_settings.items():
            if key.startswith('TOKENIZERS_') or key in ['PYTHONUNBUFFERED', 'PYTHONDONTWRITEBYTECODE']:
                if key in os.environ:
                    del os.environ[key]
                logger.info(f"Removed environment variable: {key}")
            elif key in ['OMP_NUM_THREADS'] and value is not None:
                os.environ[key] = value
                logger.info(f"Restored environment variable: {key}={value}")
        
        self.optimized = False
        logger.info("System settings restored")


class AsyncioOptimizer:
    """asyncio optimizer."""
    
    @staticmethod
    def setup_event_loop_policy():
        """Set an optimized event loop policy."""
        try:
            # Try to use uvloop if available.
            import uvloop
            asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
            logger.info("Using uvloop event loop policy")
        except ImportError:
            logger.info("Using default event loop policy")
        
        # Set the thread pool size recommendation.
        cpu_count = multiprocessing.cpu_count()
        thread_pool_size = min(32, cpu_count * 4)  # 4 threads per CPU core, up to 32
        
        # Note: the default thread pool executor cannot be set here because
        # no event loop is running yet. This should be configured during app startup.
        logger.info(f"Recommended thread pool size: {thread_pool_size}")
    
    @staticmethod
    def optimize_gunicorn_settings() -> Dict[str, Any]:
        """Get optimized Gunicorn settings."""
        cpu_count = multiprocessing.cpu_count()
        
        return {
            # Worker configuration
            'workers': min(8, cpu_count + 1),  # Number of worker processes
            'worker_class': 'uvicorn.workers.UvicornWorker',  # Use Uvicorn worker
            'worker_connections': 2000,  # Connections per worker
            'max_requests': 5000,  # Restart worker after max requests
            'max_requests_jitter': 500,  # Random jitter
            'preload_app': True,  # Preload application
            
            # Timeout settings
            'timeout': 120,  # Worker timeout
            'keepalive': 5,  # Keep-Alive timeout
            'graceful_timeout': 30,  # Graceful shutdown timeout
            
            # Performance optimizations
            'worker_tmp_dir': '/dev/shm',  # Use memory-backed filesystem
            
            # Logging settings
            'accesslog': '-',  # Standard output
            'errorlog': '-',  # Standard error output
            'loglevel': 'info',
        }


def setup_system_optimizations():
    """Set up system optimizations."""
    # 1. System-level optimizations
    system_optimizer = SystemOptimizer()
    system_optimizer.optimize_system_settings()
    
    # 2. asyncio optimizations
    asyncio_optimizer = AsyncioOptimizer()
    asyncio_optimizer.setup_event_loop_policy()
    
    return system_optimizer


def create_performance_monitor() -> Dict[str, Any]:
    """Create performance monitoring configuration."""
    return {
        'monitor_interval': 60,  # Monitoring interval in seconds
        'metrics': {
            'memory_usage': True,
            'cpu_usage': True,
            'disk_io': True,
            'network_io': True,
            'active_connections': True,
            'request_latency': True,
            'cache_hit_rate': True,
            'error_rate': True,
        },
        'alerts': {
            'memory_threshold': 0.9,  # Alert at 90% memory usage
            'cpu_threshold': 0.8,     # Alert at 80% CPU usage
            'disk_threshold': 0.9,    # Alert at 90% disk usage
            'error_threshold': 0.05,  # Alert at 5% error rate
            'latency_threshold': 5.0, # Alert at 5 seconds latency
        }
    }


def get_optimized_worker_config() -> Dict[str, Any]:
    """Get optimized worker configuration."""
    cpu_count = multiprocessing.cpu_count()
    memory_gb = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024.0 ** 3)
    
    # Configuration constrained by available system resources.
    max_workers = min(
        16,  # Maximum worker count
        max(2, cpu_count),  # At least 2 workers, at most the CPU core count
        int(memory_gb / 2)  # Memory-based worker limit, 2 GB per worker
    )
    
    return {
        'max_workers': max_workers,
        'worker_connections': 1000,  # Connections per worker
        'connection_pool_size': 100,  # Connection pool size
        'buffer_size': 8192,  # Buffer size
        'timeout': 120,  # Timeout in seconds
        'keepalive_timeout': 30,  # Keep-Alive timeout
    }


# Predefined optimization profiles
OPTIMIZATION_CONFIGS = {
    'low_memory': {
        'max_workers': 2,
        'worker_connections': 500,
        'buffer_size': 4096,
        'cache_size': 500,
    },
    'balanced': {
        'max_workers': 4,
        'worker_connections': 1000,
        'buffer_size': 8192,
        'cache_size': 1000,
    },
    'high_performance': {
        'max_workers': 8,
        'worker_connections': 2000,
        'buffer_size': 16384,
        'cache_size': 2000,
    }
}


def apply_optimization_profile(profile_name: str) -> Dict[str, Any]:
    """Apply an optimization profile."""
    if profile_name not in OPTIMIZATION_CONFIGS:
        raise ValueError(f"Unknown optimization profile: {profile_name}")
    
    config = OPTIMIZATION_CONFIGS[profile_name].copy()
    
    # Add system-specific configuration.
    config.update({
        'profile_name': profile_name,
        'applied_at': time.time(),
        'cpu_count': multiprocessing.cpu_count(),
        'memory_gb': os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024.0 ** 3)
    })
    
    return config


# Global system optimizer instance
_global_system_optimizer: Optional[SystemOptimizer] = None


def get_global_system_optimizer() -> SystemOptimizer:
    """Get the global system optimizer."""
    global _global_system_optimizer
    if _global_system_optimizer is None:
        _global_system_optimizer = SystemOptimizer()
    return _global_system_optimizer