# ==============================================================================
# ADTLAS DAI MANAGEMENT SYSTEM - GUNICORN CONFIGURATION
# ==============================================================================
#
# This file contains the Gunicorn WSGI server configuration for the Adtlas
# Django application. It's optimized for production use with proper logging,
# worker management, and performance tuning.
#
# Gunicorn is a Python WSGI HTTP Server for UNIX that serves the Django
# application. This configuration ensures optimal performance, reliability,
# and monitoring capabilities.
#
# Author: Adtlas Development Team
# Version: 2.0.0
# Last Updated: 2025-01-20
# ==============================================================================

import os
import multiprocessing
from pathlib import Path

# ==============================================================================
# SERVER SOCKET CONFIGURATION
# ==============================================================================

# The socket to bind to
# Format: HOST:PORT or unix:PATH
# Using 0.0.0.0 to accept connections from any IP (required for Docker)
bind = os.getenv('GUNICORN_BIND', '0.0.0.0:8000')

# The maximum number of pending connections
# This should be set to a high number for high traffic applications
backlog = int(os.getenv('GUNICORN_BACKLOG', '2048'))

# ==============================================================================
# WORKER PROCESSES CONFIGURATION
# ==============================================================================

# The number of worker processes for handling requests
# Rule of thumb: (2 x CPU cores) + 1
# Can be overridden by environment variable for container orchestration
workers = int(os.getenv('GUNICORN_WORKERS', multiprocessing.cpu_count() * 2 + 1))

# The type of workers to run
# Options: sync, eventlet, gevent, tornado, gthread
# gevent is recommended for I/O bound applications like Django
worker_class = os.getenv('GUNICORN_WORKER_CLASS', 'gevent')

# The maximum number of simultaneous clients per worker (for async workers)
# Only applies to gevent and eventlet worker classes
worker_connections = int(os.getenv('GUNICORN_WORKER_CONNECTIONS', '1000'))

# The maximum number of requests a worker will process before restarting
# This helps prevent memory leaks in long-running processes
max_requests = int(os.getenv('GUNICORN_MAX_REQUESTS', '1000'))

# Randomize max_requests to prevent all workers from restarting at once
# This adds jitter to prevent thundering herd problems
max_requests_jitter = int(os.getenv('GUNICORN_MAX_REQUESTS_JITTER', '100'))

# Workers silent for more than this many seconds are killed and restarted
# Should be higher than your longest expected request processing time
timeout = int(os.getenv('GUNICORN_TIMEOUT', '120'))

# Timeout for graceful workers restart
# Workers that don't exit gracefully within this time are forcefully killed
graceful_timeout = int(os.getenv('GUNICORN_GRACEFUL_TIMEOUT', '30'))

# The number of seconds to wait for requests on a Keep-Alive connection
keepalive = int(os.getenv('GUNICORN_KEEPALIVE', '5'))

# ==============================================================================
# APPLICATION CONFIGURATION
# ==============================================================================

# The Python path to the WSGI module and callable
# This points to the Django WSGI application
wsgi_module = 'core.wsgi:application'

# Preload application code before forking worker processes
# This can save memory and improve startup time
preload_app = os.getenv('GUNICORN_PRELOAD', 'True').lower() == 'true'

# Restart workers when code changes (development only)
# Should be False in production for security and performance
reload = os.getenv('GUNICORN_RELOAD', 'False').lower() == 'true'

# Files to watch for changes when reload is enabled
# Only used when reload=True
reload_extra_files = []
if reload:
    # Add Python files to watch list
    reload_extra_files.extend([
        '/app/src/core/settings.py',
        '/app/src/core/urls.py',
        '/app/src/core/wsgi.py',
    ])

# ==============================================================================
# SECURITY CONFIGURATION
# ==============================================================================

# Limit the allowed size of an HTTP request header field
# Helps prevent certain types of attacks
limit_request_field_size = int(os.getenv('GUNICORN_LIMIT_REQUEST_FIELD_SIZE', '8190'))

# Limit the number of header fields in a request
# Helps prevent header-based attacks
limit_request_fields = int(os.getenv('GUNICORN_LIMIT_REQUEST_FIELDS', '100'))

# Limit the allowed size of an HTTP request line
# Helps prevent URL-based attacks
limit_request_line = int(os.getenv('GUNICORN_LIMIT_REQUEST_LINE', '4094'))

# ==============================================================================
# LOGGING CONFIGURATION
# ==============================================================================

# Create logs directory if it doesn't exist
logs_dir = Path('/app/logs')
logs_dir.mkdir(exist_ok=True)

# The granularity of Error log outputs
# Valid levels: debug, info, warning, error, critical
loglevel = os.getenv('GUNICORN_LOG_LEVEL', 'info')

# The Access log file to write to
# Use '-' to log to stdout (useful for Docker)
accesslog = os.getenv('GUNICORN_ACCESS_LOG', '/app/logs/gunicorn-access.log')

# The Error log file to write to
# Use '-' to log to stderr (useful for Docker)
errorlog = os.getenv('GUNICORN_ERROR_LOG', '/app/logs/gunicorn-error.log')

# Redirect stdout/stderr to specified files
# Useful for capturing print statements and uncaught exceptions
capture_output = os.getenv('GUNICORN_CAPTURE_OUTPUT', 'True').lower() == 'true'

# The access log format
# This format includes useful information for monitoring and debugging
access_log_format = os.getenv(
    'GUNICORN_ACCESS_LOG_FORMAT',
    '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
)

# Disable access log for health check endpoints to reduce noise
# This is a custom function that can be used to filter access logs
def skip_health_checks(record):
    """
    Skip logging for health check endpoints to reduce log noise.
    
    Args:
        record: The log record to evaluate
        
    Returns:
        bool: True if the record should be logged, False to skip
    """
    # Skip health check endpoints
    health_endpoints = ['/health/', '/ping/', '/status/']
    
    # Get the request path from the log record
    request_line = getattr(record, 'args', {}).get('r', '')
    
    for endpoint in health_endpoints:
        if endpoint in request_line:
            return False
    
    return True

# Apply the filter if configured
if os.getenv('GUNICORN_SKIP_HEALTH_LOGS', 'True').lower() == 'true':
    access_log_format = access_log_format

# ==============================================================================
# PROCESS NAMING
# ==============================================================================

# A base to use with setproctitle for process naming
# This helps identify Gunicorn processes in system monitoring tools
proc_name = os.getenv('GUNICORN_PROC_NAME', 'adtlas-django')

# ==============================================================================
# SSL CONFIGURATION (if needed)
# ==============================================================================

# SSL certificate file path (if using HTTPS)
keyfile = os.getenv('GUNICORN_KEYFILE', None)
certfile = os.getenv('GUNICORN_CERTFILE', None)

# SSL version to use
# Options: SSLv3, SSLv23, TLSv1, TLSv1_1, TLSv1_2
ssl_version = os.getenv('GUNICORN_SSL_VERSION', 'TLSv1_2')

# Whether to perform SSL handshake on socket connect
do_handshake_on_connect = os.getenv('GUNICORN_DO_HANDSHAKE_ON_CONNECT', 'False').lower() == 'true'

# ==============================================================================
# PERFORMANCE TUNING
# ==============================================================================

# Enable sendfile() for serving static files (if not using nginx)
# Should be False when using a reverse proxy like nginx
sendfile = os.getenv('GUNICORN_SENDFILE', 'False').lower() == 'true'

# Enable reuse_port for better load balancing across workers
# Only available on Linux kernel 3.9+
reuse_port = os.getenv('GUNICORN_REUSE_PORT', 'True').lower() == 'true'

# ==============================================================================
# MONITORING AND HEALTH CHECKS
# ==============================================================================

# Enable stats collection for monitoring
# This can be used by monitoring tools to collect metrics
statsd_host = os.getenv('STATSD_HOST', None)
statsd_prefix = os.getenv('STATSD_PREFIX', 'adtlas.gunicorn')

# ==============================================================================
# HOOKS AND CALLBACKS
# ==============================================================================

def on_starting(server):
    """
    Called just before the master process is initialized.
    
    Args:
        server: The Gunicorn server instance
    """
    server.log.info("=== Adtlas Django Application Starting ===")
    server.log.info(f"Gunicorn version: {server.version}")
    server.log.info(f"Python version: {os.sys.version}")
    server.log.info(f"Workers: {workers}")
    server.log.info(f"Worker class: {worker_class}")
    server.log.info(f"Bind: {bind}")
    server.log.info(f"Timeout: {timeout}")
    server.log.info(f"Preload app: {preload_app}")

def on_reload(server):
    """
    Called to recycle workers during a reload via SIGHUP.
    
    Args:
        server: The Gunicorn server instance
    """
    server.log.info("Reloading Adtlas Django application...")

def when_ready(server):
    """
    Called just after the server is started.
    
    Args:
        server: The Gunicorn server instance
    """
    server.log.info("=== Adtlas Django Application Ready ===")
    server.log.info(f"Server is ready to accept connections on {bind}")

def worker_int(worker):
    """
    Called just after a worker exited on SIGINT or SIGQUIT.
    
    Args:
        worker: The worker instance
    """
    worker.log.info(f"Worker {worker.pid} interrupted")

def pre_fork(server, worker):
    """
    Called just before a worker is forked.
    
    Args:
        server: The Gunicorn server instance
        worker: The worker instance
    """
    server.log.debug(f"Worker {worker.pid} about to be forked")

def post_fork(server, worker):
    """
    Called just after a worker has been forked.
    
    Args:
        server: The Gunicorn server instance
        worker: The worker instance
    """
    server.log.debug(f"Worker {worker.pid} forked successfully")

def post_worker_init(worker):
    """
    Called just after a worker has initialized the application.
    
    Args:
        worker: The worker instance
    """
    worker.log.debug(f"Worker {worker.pid} initialized")

def worker_abort(worker):
    """
    Called when a worker received the SIGABRT signal.
    
    Args:
        worker: The worker instance
    """
    worker.log.error(f"Worker {worker.pid} aborted")

def pre_exec(server):
    """
    Called just before a new master process is forked.
    
    Args:
        server: The Gunicorn server instance
    """
    server.log.info("Forked child, re-executing.")

def pre_request(worker, req):
    """
    Called just before a worker processes the request.
    
    Args:
        worker: The worker instance
        req: The request instance
    """
    # Log slow requests (optional)
    worker.log.debug(f"Processing request: {req.method} {req.path}")

def post_request(worker, req, environ, resp):
    """
    Called after a worker processes the request.
    
    Args:
        worker: The worker instance
        req: The request instance
        environ: The WSGI environ dict
        resp: The response instance
    """
    # Log response status for debugging (optional)
    worker.log.debug(f"Request completed: {req.method} {req.path} - {resp.status}")

def child_exit(server, worker):
    """
    Called just after a worker has been exited, in the master process.
    
    Args:
        server: The Gunicorn server instance
        worker: The worker instance
    """
    server.log.info(f"Worker {worker.pid} exited")

def worker_exit(server, worker):
    """
    Called just after a worker has been exited, in the worker process.
    
    Args:
        server: The Gunicorn server instance
        worker: The worker instance
    """
    worker.log.info(f"Worker {worker.pid} exiting")

def nworkers_changed(server, new_value, old_value):
    """
    Called just after num_workers has been changed.
    
    Args:
        server: The Gunicorn server instance
        new_value: The new number of workers
        old_value: The old number of workers
    """
    server.log.info(f"Number of workers changed from {old_value} to {new_value}")

def on_exit(server):
    """
    Called just before exiting Gunicorn.
    
    Args:
        server: The Gunicorn server instance
    """
    server.log.info("=== Adtlas Django Application Shutting Down ===")

# ==============================================================================
# CONFIGURATION SUMMARY
# ==============================================================================

# This configuration provides:
# 1. Optimal worker configuration for Django applications
# 2. Comprehensive logging for monitoring and debugging
# 3. Security hardening with request limits
# 4. Performance tuning for production use
# 5. Graceful handling of worker lifecycle events
# 6. SSL support for secure connections
# 7. Integration with monitoring systems
# 8. Detailed hooks for application lifecycle management
#
# For more information, see:
# - Gunicorn documentation: https://docs.gunicorn.org/
# - Django deployment: https://docs.djangoproject.com/en/stable/howto/deployment/
# ==============================================================================