#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

"""
Gunicorn Configuration - Celery Replacement Example

This configuration sets up:
1. ASGI workers to handle web requests with async I/O (using uvloop)
2. Dirty workers to handle background tasks (replacing Celery workers)

Why ASGI + Dirty Arbiters?
- ASGI: Non-blocking HTTP handling - one worker handles many concurrent requests
- Dirty: Stateful background workers - keep models/connections loaded in memory

Comparison with Celery deployment:
- Celery: gunicorn app:app + celery -A tasks worker + redis-server
- Dirty: gunicorn -c gunicorn_conf.py app:app (single command, no broker!)
"""

import multiprocessing
import os

# =============================================================================
# Basic Settings
# =============================================================================

# Bind to all interfaces on port 8000
bind = os.environ.get("GUNICORN_BIND", "0.0.0.0:8000")

# HTTP workers - handle incoming web requests
# With ASGI, fewer workers needed since each handles many concurrent requests
workers = int(os.environ.get("GUNICORN_WORKERS", min(4, multiprocessing.cpu_count() + 1)))

# Use gunicorn's native ASGI worker for async support
# This enables: await client.execute_async() without blocking
worker_class = "asgi"

# Use uvloop for better async performance
asgi_loop = "uvloop"

# Maximum concurrent connections per worker
worker_connections = 1000

# =============================================================================
# Dirty Arbiter Settings (Celery Worker Replacement)
# =============================================================================

# Task workers - these replace Celery workers
# Each dirty app can specify its own worker count via the `workers` class attribute
dirty_apps = [
    # Email tasks - 2 workers (I/O bound)
    "examples.celery_alternative.tasks:EmailWorker",
    # Image processing - 2 workers (CPU/memory intensive)
    "examples.celery_alternative.tasks:ImageWorker",
    # Data processing - 4 workers (parallelizable)
    "examples.celery_alternative.tasks:DataWorker",
    # Scheduled tasks - 1 worker
    "examples.celery_alternative.tasks:ScheduledWorker",
]

# Total dirty workers (distributed among apps based on their `workers` attribute)
# If not set, uses sum of all app worker counts
dirty_workers = int(os.environ.get("DIRTY_WORKERS", 9))  # 2+2+4+1 = 9

# Task timeout in seconds (like Celery's task_time_limit)
dirty_timeout = int(os.environ.get("DIRTY_TIMEOUT", 300))

# Threads per dirty worker (for concurrent task execution)
dirty_threads = int(os.environ.get("DIRTY_THREADS", 1))

# Graceful shutdown timeout
dirty_graceful_timeout = int(os.environ.get("DIRTY_GRACEFUL_TIMEOUT", 30))

# =============================================================================
# Timeouts & Limits
# =============================================================================

# Worker timeout (seconds)
timeout = 120

# Keep-alive connections
keepalive = 5

# Maximum requests per worker before recycling
max_requests = 1000
max_requests_jitter = 50

# =============================================================================
# Logging
# =============================================================================

# Log level
loglevel = os.environ.get("LOG_LEVEL", "info")

# Access log format
accesslog = "-"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Error log
errorlog = "-"

# =============================================================================
# Lifecycle Hooks
# =============================================================================

def on_starting(server):
    """Called just before the master process is initialized."""
    print("=" * 60)
    print("Starting Gunicorn with Dirty Arbiters (Celery Replacement)")
    print("Using ASGI workers with uvloop for non-blocking HTTP handling")
    print("=" * 60)


def on_dirty_starting(arbiter):
    """Called when the dirty arbiter is starting."""
    print(f"[Dirty] Starting dirty arbiter")
    print(f"[Dirty] Registered apps: {list(arbiter.cfg.dirty_apps)}")


def dirty_post_fork(arbiter, worker):
    """Called after a dirty worker is forked."""
    print(f"[Dirty] Worker {worker.pid} started")


def dirty_worker_init(worker):
    """Called when a dirty worker initializes its apps."""
    print(f"[Dirty] Worker {worker.pid} initialized apps: {list(worker.apps.keys())}")


def dirty_worker_exit(arbiter, worker):
    """Called when a dirty worker exits."""
    print(f"[Dirty] Worker {worker.pid} exiting")


def worker_int(worker):
    """Called when a worker receives SIGINT."""
    print(f"[HTTP] Worker {worker.pid} interrupted")


def worker_exit(server, worker):
    """Called when a worker exits."""
    print(f"[HTTP] Worker {worker.pid} exited")


# =============================================================================
# Development vs Production
# =============================================================================

# Reload on code changes (development only)
reload = os.environ.get("GUNICORN_RELOAD", "false").lower() == "true"

# Preload app for faster worker startup (production)
preload_app = os.environ.get("GUNICORN_PRELOAD", "false").lower() == "true"
