# Gunicorn configuration file for Ad-tlas Django application
# Production-ready configuration with performance optimizations

import multiprocessing
import os
from pathlib import Path

# Server socket
bind = "0.0.0.0:8000"
backlog = 2048

# Worker processes
workers = int(os.environ.get('GUNICORN_WORKERS', multiprocessing.cpu_count() * 2 + 1))
worker_class = os.environ.get('GUNICORN_WORKER_CLASS', 'gevent')
worker_connections = int(os.environ.get('GUNICORN_WORKER_CONNECTIONS', 1000))
max_requests = int(os.environ.get('GUNICORN_MAX_REQUESTS', 1000))
max_requests_jitter = int(os.environ.get('GUNICORN_MAX_REQUESTS_JITTER', 100))
preload_app = True

# Worker timeout and keepalive
timeout = int(os.environ.get('GUNICORN_TIMEOUT', 30))
keepalive = int(os.environ.get('GUNICORN_KEEPALIVE', 2))
worker_tmp_dir = '/dev/shm'  # Use memory for worker temporary files

# Logging
accesslog = os.environ.get('GUNICORN_ACCESS_LOG', '/app/logs/gunicorn-access.log')
errorlog = os.environ.get('GUNICORN_ERROR_LOG', '/app/logs/gunicorn-error.log')
loglevel = os.environ.get('GUNICORN_LOG_LEVEL', 'info')
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Process naming
proc_name = 'adtlas-gunicorn'

# Server mechanics
daemon = False
pidfile = '/app/logs/gunicorn.pid'
user = None  # Run as current user in container
group = None
tmp_upload_dir = None

# SSL (if needed)
keyfile = os.environ.get('GUNICORN_KEYFILE')
certfile = os.environ.get('GUNICORN_CERTFILE')
ssl_version = 2  # SSLv23
ciphers = 'TLSv1'
do_handshake_on_connect = False
suppress_ragged_eofs = True

# Security
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190

# Performance tuning
worker_rlimit_nofile = 65535
worker_rlimit_core = 0

# Application specific
raw_env = [
    'DJANGO_SETTINGS_MODULE=config.settings.production',
    f'PYTHONPATH=/app/src',
]

# Hooks for application lifecycle
def on_starting(server):
    """Called just before the master process is initialized."""
    server.log.info("Starting Ad-tlas application server")
    
    # Create log directory if it doesn't exist
    log_dir = Path('/app/logs')
    log_dir.mkdir(exist_ok=True)
    
    # Set environment variables
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')

def on_reload(server):
    """Called to recycle workers during a reload via SIGHUP."""
    server.log.info("Reloading Ad-tlas application server")

def when_ready(server):
    """Called just after the server is started."""
    server.log.info("Ad-tlas application server is ready. Listening on: %s", server.address)
    server.log.info("Using %s workers of class %s", server.cfg.workers, server.cfg.worker_class)

def worker_int(worker):
    """Called just after a worker exited on SIGINT or SIGQUIT."""
    worker.log.info("Worker received INT or QUIT signal")

def pre_fork(server, worker):
    """Called just before a worker is forked."""
    server.log.debug("Worker spawned (pid: %s)", worker.pid)

def post_fork(server, worker):
    """Called just after a worker has been forked."""
    server.log.debug("Worker spawned (pid: %s)", worker.pid)
    
    # Set worker process title
    try:
        import setproctitle
        setproctitle.setproctitle(f'adtlas-worker-{worker.age}')
    except ImportError:
        pass

def post_worker_init(worker):
    """Called just after a worker has initialized the application."""
    worker.log.debug("Worker initialized (pid: %s)", worker.pid)

def worker_abort(worker):
    """Called when a worker received the SIGABRT signal."""
    worker.log.error("Worker received SIGABRT signal (pid: %s)", worker.pid)

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

def pre_request(worker, req):
    """Called just before a worker processes the request."""
    worker.log.debug("%s %s", req.method, req.path)

def post_request(worker, req, environ, resp):
    """Called after a worker processes the request."""
    # Log slow requests
    request_time = getattr(req, 'start_time', None)
    if request_time:
        duration = time.time() - request_time
        if duration > 1.0:  # Log requests taking more than 1 second
            worker.log.warning(
                "Slow request: %s %s - %s seconds",
                req.method, req.path, duration
            )

def child_exit(server, worker):
    """Called just after a worker has been exited, in the master process."""
    server.log.info("Worker exited (pid: %s)", worker.pid)

def worker_exit(server, worker):
    """Called just after a worker has been exited, in the worker process."""
    worker.log.info("Worker exiting (pid: %s)", worker.pid)

def nworkers_changed(server, new_value, old_value):
    """Called just after num_workers has been changed."""
    server.log.info("Number of workers changed from %s to %s", old_value, new_value)

def on_exit(server):
    """Called just before exiting."""
    server.log.info("Shutting down Ad-tlas application server")

# Custom application class for Django
def application(environ, start_response):
    """Custom WSGI application wrapper."""
    # Import here to avoid issues with Django setup
    from django.core.wsgi import get_wsgi_application
    
    # Set up Django
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
    
    # Get the Django WSGI application
    django_app = get_wsgi_application()
    
    return django_app(environ, start_response)

# Environment-specific configurations
if os.environ.get('DJANGO_ENV') == 'development':
    # Development overrides
    reload = True
    loglevel = 'debug'
    workers = 1
    timeout = 0  # Disable timeout in development
    
elif os.environ.get('DJANGO_ENV') == 'staging':
    # Staging overrides
    workers = max(2, multiprocessing.cpu_count())
    loglevel = 'info'
    
elif os.environ.get('DJANGO_ENV') == 'production':
    # Production overrides (already set above)
    pass

# Memory and resource limits
if worker_class == 'gevent':
    # Gevent-specific settings
    worker_connections = 1000
    max_requests = 1000
    max_requests_jitter = 100
    
elif worker_class == 'sync':
    # Sync worker settings
    worker_connections = 1
    max_requests = 1000
    max_requests_jitter = 100
    
elif worker_class == 'gthread':
    # Thread worker settings
    threads = int(os.environ.get('GUNICORN_THREADS', 2))
    worker_connections = 1000
    max_requests = 1000
    max_requests_jitter = 100

# Validate configuration
if workers < 1:
    workers = 1
    
if timeout < 0:
    timeout = 30
    
if max_requests < 0:
    max_requests = 1000

# Import time for request timing
import time

# Add request timing to environ
def pre_request_hook(worker, req):
    req.start_time = time.time()

pre_request = pre_request_hook

# Configuration summary logging
def log_config(server):
    """Log the current configuration."""
    server.log.info("=" * 50)
    server.log.info("Ad-tlas Gunicorn Configuration")
    server.log.info("=" * 50)
    server.log.info("Workers: %s", workers)
    server.log.info("Worker class: %s", worker_class)
    server.log.info("Worker connections: %s", worker_connections)
    server.log.info("Max requests: %s", max_requests)
    server.log.info("Timeout: %s", timeout)
    server.log.info("Bind: %s", bind)
    server.log.info("Log level: %s", loglevel)
    server.log.info("Environment: %s", os.environ.get('DJANGO_ENV', 'unknown'))
    server.log.info("=" * 50)

# Override when_ready to include config logging
original_when_ready = when_ready

def when_ready(server):
    original_when_ready(server)
    log_config(server)