#!/usr/bin/env bash
#───────────────────────────────────────────────────────────────────────────────
#  Script : run-granian.sh
#  Author : Mihai Criveti
#  Purpose: Launch ContextForge API under Granian (Rust-based ASGI server)
#
#  Description:
#    This script provides a robust way to launch a production API server using
#    Granian with the following features:
#
#    - Rust-based HTTP server (Hyper + Tokio) for high performance
#    - Native HTTP/2 support
#    - Native WebSocket support
#    - Native mTLS support
#    - Portable Python detection across different distros
#    - Virtual environment handling
#    - Configurable via environment variables for CI/CD pipelines
#    - Optional TLS/SSL support for secure connections
#    - Process lock to prevent duplicate instances
#    - Auto-detection of optimal worker count based on CPU cores
#
#  Environment Variables:
#    PYTHON                        : Path to Python interpreter (optional)
#    VIRTUAL_ENV                   : Path to active virtual environment (auto-detected)
#    HOST                         : Bind host (default: 0.0.0.0)
#    PORT                         : Bind port (default: 4444)
#    GRANIAN_WORKERS              : Number of worker processes (default: "auto" = CPU cores, max 16)
#    GRANIAN_RUNTIME_MODE         : Runtime mode: auto, mt, st (default: mt for >8 workers, else st)
#    GRANIAN_RUNTIME_THREADS      : Runtime threads per worker (default: 1)
#    GRANIAN_BLOCKING_THREADS     : Blocking threads per worker (default: 1)
#    GRANIAN_HTTP                 : HTTP version: auto, 1, 2 (default: auto)
#    GRANIAN_LOOP                 : Event loop: uvloop, asyncio, rloop (default: uvloop)
#    GRANIAN_TASK_IMPL            : Task implementation: asyncio, rust (default: auto-detect)
#    GRANIAN_HTTP1_PIPELINE_FLUSH : Enable HTTP/1 pipeline flush (default: true)
#    GRANIAN_HTTP1_BUFFER_SIZE    : HTTP/1 buffer size in bytes (default: 524288)
#    GRANIAN_BACKLOG              : Connection backlog (default: 2048)
#    GRANIAN_BACKPRESSURE         : Max concurrent requests per worker (default: 512)
#    GRANIAN_RESPAWN_FAILED       : Respawn failed workers (default: true)
#    GRANIAN_WORKERS_LIFETIME     : Max worker lifetime before respawn (default: disabled, min 60s)
#    GRANIAN_WORKERS_MAX_RSS      : Max worker RSS memory in MiB before respawn (default: disabled)
#    GRANIAN_DEV_MODE             : Enable hot reload (default: false, requires granian[reload])
#    GRANIAN_LOG_LEVEL            : Log level: debug, info, warning, error (default: info)
#    SSL                          : Enable TLS/SSL (true/false, default: false)
#    CERT_FILE                    : Path to SSL certificate (default: certs/cert.pem)
#    KEY_FILE                     : Path to SSL private key (default: certs/key.pem)
#    FORCE_START                  : Force start even if another instance is running (default: false)
#    DISABLE_ACCESS_LOG           : Disable access logging for performance (default: true)
#
#  Usage:
#    ./run-granian.sh                     # Run with defaults
#    SSL=true ./run-granian.sh            # Run with TLS enabled
#    GRANIAN_WORKERS=16 ./run-granian.sh  # Run with 16 workers
#    GRANIAN_HTTP=2 ./run-granian.sh      # Force HTTP/2
#    GRANIAN_DEV_MODE=true ./run-granian.sh # Run with hot reload
#    FORCE_START=true ./run-granian.sh    # Force start (bypass lock check)
#
#  Tuning Profiles (adjust based on workload):
#    # High-throughput (fewer workers, more threads per worker)
#    GRANIAN_WORKERS=4 GRANIAN_RUNTIME_THREADS=4 ./run-granian.sh
#
#    # High-concurrency (more workers, max backpressure)
#    GRANIAN_WORKERS=16 GRANIAN_BACKPRESSURE=1024 GRANIAN_BACKLOG=4096 ./run-granian.sh
#
#    # Memory-constrained (fewer workers)
#    GRANIAN_WORKERS=2 ./run-granian.sh
#
#    # SSE workloads (worker recycling to prevent connection leaks)
#    # Workaround for https://github.com/emmett-framework/granian/issues/286
#    GRANIAN_WORKERS_LIFETIME=3600 GRANIAN_WORKERS_MAX_RSS=512 ./run-granian.sh
#───────────────────────────────────────────────────────────────────────────────

# Exit immediately on error, undefined variable, or pipe failure
set -euo pipefail

#────────────────────────────────────────────────────────────────────────────────
# SECTION 1: Script Location Detection
#────────────────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cd "${SCRIPT_DIR}" || {
    echo "❌  FATAL: Cannot change to script directory: ${SCRIPT_DIR}"
    exit 1
}

#────────────────────────────────────────────────────────────────────────────────
# SECTION 2: Process Lock Check
#────────────────────────────────────────────────────────────────────────────────
LOCK_FILE="/tmp/mcpgateway-granian.lock"
FORCE_START=${FORCE_START:-false}

check_existing_process() {
    if [[ -f "${LOCK_FILE}" ]]; then
        local pid
        pid=$(<"${LOCK_FILE}")

        if kill -0 "${pid}" 2>/dev/null; then
            echo "⚠️  WARNING: Another instance of ContextForge appears to be running (PID: ${pid})"

            if ps -p "${pid}" -o comm= | grep -q granian; then
                if [[ "${FORCE_START}" != "true" ]]; then
                    echo "❌  FATAL: ContextForge is already running!"
                    echo "   To stop it: kill ${pid}"
                    echo "   To force start anyway: FORCE_START=true $0"
                    exit 1
                else
                    echo "⚠️  Force starting despite existing process..."
                fi
            else
                echo "🔧  Lock file exists but process ${pid} is not granian. Cleaning up..."
                rm -f "${LOCK_FILE}"
            fi
        else
            echo "🔧  Stale lock file found. Cleaning up..."
            rm -f "${LOCK_FILE}"
        fi
    fi
}

cleanup() {
    if [[ -f "${LOCK_FILE}" ]] && [[ "$(<"${LOCK_FILE}")" == "$$" ]]; then
        rm -f "${LOCK_FILE}"
        echo "🔧  Cleaned up lock file"
    fi
}

trap cleanup INT TERM
check_existing_process
echo $$ > "${LOCK_FILE}"

#────────────────────────────────────────────────────────────────────────────────
# SECTION 3: Virtual Environment Activation
#────────────────────────────────────────────────────────────────────────────────
if [[ -z "${VIRTUAL_ENV:-}" ]]; then
    if [[ -f "${HOME}/.venv/mcpgateway/bin/activate" ]]; then
        echo "🔧  Activating virtual environment: ${HOME}/.venv/mcpgateway"
        # shellcheck disable=SC1090
        source "${HOME}/.venv/mcpgateway/bin/activate"
    elif [[ -f "${SCRIPT_DIR}/.venv/bin/activate" ]]; then
        echo "🔧  Activating virtual environment in script directory"
        # shellcheck disable=SC1090
        source "${SCRIPT_DIR}/.venv/bin/activate"
    else
        echo "⚠️  WARNING: No virtual environment found!"
        echo "   Consider creating one with: python3 -m venv ~/.venv/mcpgateway"
    fi
else
    echo "✓  Virtual environment already active: ${VIRTUAL_ENV}"
fi

#────────────────────────────────────────────────────────────────────────────────
# SECTION 4: Python Interpreter Detection
#────────────────────────────────────────────────────────────────────────────────
if [[ -z "${PYTHON:-}" ]]; then
    if [[ -n "${VIRTUAL_ENV:-}" && -x "${VIRTUAL_ENV}/bin/python" ]]; then
        PYTHON="${VIRTUAL_ENV}/bin/python"
        echo "🐍  Using Python from virtual environment"
    else
        if command -v python3 &> /dev/null; then
            PYTHON="$(command -v python3)"
            echo "🐍  Found system Python3: ${PYTHON}"
        elif command -v python &> /dev/null; then
            PYTHON="$(command -v python)"
            echo "🐍  Found system Python: ${PYTHON}"
        else
            PYTHON=""
        fi
    fi
fi

if [[ -z "${PYTHON}" ]] || [[ ! -x "${PYTHON}" ]]; then
    echo "❌  FATAL: Could not locate a Python interpreter!"
    exit 1
fi

PY_VERSION="$("${PYTHON}" --version 2>&1)"
echo "📋  Python version: ${PY_VERSION}"

if ! "${PYTHON}" -c "import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)" 2>/dev/null; then
    echo "❌  FATAL: Python 3.x is required!"
    exit 1
fi

#────────────────────────────────────────────────────────────────────────────────
# SECTION 5: Display Application Banner
#────────────────────────────────────────────────────────────────────────────────
cat <<'EOF'
███╗   ███╗ ██████╗██████╗      ██████╗  █████╗ ████████╗███████╗██╗    ██╗ █████╗ ██╗   ██╗
████╗ ████║██╔════╝██╔══██╗    ██╔════╝ ██╔══██╗╚══██╔══╝██╔════╝██║    ██║██╔══██╗╚██╗ ██╔╝
██╔████╔██║██║     ██████╔╝    ██║  ███╗███████║   ██║   █████╗  ██║ █╗ ██║███████║ ╚████╔╝
██║╚██╔╝██║██║     ██╔═══╝     ██║   ██║██╔══██║   ██║   ██╔══╝  ██║███╗██║██╔══██║  ╚██╔╝
██║ ╚═╝ ██║╚██████╗██║         ╚██████╔╝██║  ██║   ██║   ███████╗╚███╔███╔╝██║  ██║   ██║
╚═╝     ╚═╝ ╚═════╝╚═╝          ╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚══╝╚══╝ ╚═╝  ╚═╝   ╚═╝
                                                                    ⚡ Powered by Granian
EOF

#────────────────────────────────────────────────────────────────────────────────
# SECTION 6: Configure Granian Settings
#────────────────────────────────────────────────────────────────────────────────

# Host and port configuration
HOST=${HOST:-0.0.0.0}
PORT=${PORT:-4444}

# Worker configuration
if [[ -z "${GRANIAN_WORKERS:-}" || "${GRANIAN_WORKERS}" == "auto" ]]; then
    if command -v nproc &>/dev/null; then
        CPU_COUNT=$(nproc)
    elif command -v sysctl &>/dev/null && sysctl -n hw.ncpu &>/dev/null; then
        CPU_COUNT=$(sysctl -n hw.ncpu)
    else
        CPU_COUNT=4
    fi
    # Granian recommends 1 worker per CPU core (it's more efficient than Gunicorn)
    GRANIAN_WORKERS=$((CPU_COUNT > 16 ? 16 : CPU_COUNT))
    echo "🔧  Auto-detected CPU cores: ${CPU_COUNT} → Workers: ${GRANIAN_WORKERS}"
fi

# Threading configuration
GRANIAN_RUNTIME_THREADS=${GRANIAN_RUNTIME_THREADS:-1}
GRANIAN_BLOCKING_THREADS=${GRANIAN_BLOCKING_THREADS:-1}

# Runtime mode: auto, mt (multi-threaded), st (single-threaded)
# mt mode scales better with many CPUs (>8 cores)
if [[ -z "${GRANIAN_RUNTIME_MODE:-}" ]]; then
    if [[ "${GRANIAN_WORKERS}" -gt 8 ]]; then
        GRANIAN_RUNTIME_MODE="mt"
    else
        GRANIAN_RUNTIME_MODE="st"
    fi
fi

# HTTP version: auto, 1, 2
GRANIAN_HTTP=${GRANIAN_HTTP:-auto}

# Event loop: auto, asyncio, uvloop, rloop
# uvloop provides best performance on Linux/macOS (installed via granian[uvloop])
GRANIAN_LOOP=${GRANIAN_LOOP:-uvloop}

# Task implementation: asyncio or rust
# rust provides faster async task scheduling but only works on Python < 3.12
# Auto-detect: use rust on Python < 3.12, asyncio otherwise
if [[ -z "${GRANIAN_TASK_IMPL:-}" ]]; then
    PY_MINOR=$("${PYTHON}" -c "import sys; print(sys.version_info.minor)")
    if [[ "${PY_MINOR}" -lt 12 ]]; then
        GRANIAN_TASK_IMPL="rust"
    else
        GRANIAN_TASK_IMPL="asyncio"
    fi
fi

# HTTP/1 optimizations
GRANIAN_HTTP1_PIPELINE_FLUSH=${GRANIAN_HTTP1_PIPELINE_FLUSH:-true}
GRANIAN_HTTP1_BUFFER_SIZE=${GRANIAN_HTTP1_BUFFER_SIZE:-524288}

# Backlog and backpressure for high concurrency
GRANIAN_BACKLOG=${GRANIAN_BACKLOG:-2048}
GRANIAN_BACKPRESSURE=${GRANIAN_BACKPRESSURE:-512}

# Developer mode with hot reload (requires granian[reload])
GRANIAN_DEV_MODE=${GRANIAN_DEV_MODE:-false}

# Respawn failed workers automatically (recommended for production)
GRANIAN_RESPAWN_FAILED=${GRANIAN_RESPAWN_FAILED:-true}

# Log level
GRANIAN_LOG_LEVEL=${GRANIAN_LOG_LEVEL:-info}

echo "📊  Granian Configuration:"
echo "   Host: ${HOST}:${PORT}"
echo "   Workers: ${GRANIAN_WORKERS}"
echo "   Runtime mode: ${GRANIAN_RUNTIME_MODE}"
echo "   Runtime threads per worker: ${GRANIAN_RUNTIME_THREADS}"
echo "   Blocking threads: ${GRANIAN_BLOCKING_THREADS}"
echo "   HTTP version: ${GRANIAN_HTTP}"
echo "   Event loop: ${GRANIAN_LOOP}"
echo "   Task implementation: ${GRANIAN_TASK_IMPL}"
echo "   HTTP/1 pipeline flush: ${GRANIAN_HTTP1_PIPELINE_FLUSH}"
echo "   HTTP/1 buffer size: ${GRANIAN_HTTP1_BUFFER_SIZE}"
echo "   Backlog: ${GRANIAN_BACKLOG}"
echo "   Backpressure: ${GRANIAN_BACKPRESSURE}"
echo "   Respawn failed workers: ${GRANIAN_RESPAWN_FAILED}"
echo "   Developer Mode: ${GRANIAN_DEV_MODE}"
echo "   Log Level: ${GRANIAN_LOG_LEVEL}"

#────────────────────────────────────────────────────────────────────────────────
# SECTION 7: Configure TLS/SSL Settings
#────────────────────────────────────────────────────────────────────────────────
SSL=${SSL:-false}
CERT_FILE=${CERT_FILE:-certs/cert.pem}
KEY_FILE=${KEY_FILE:-certs/key.pem}
KEY_FILE_PASSWORD=${KEY_FILE_PASSWORD:-}
CERT_PASSPHRASE=${CERT_PASSPHRASE:-}

# Use CERT_PASSPHRASE if KEY_FILE_PASSWORD is not set (for compatibility)
if [[ -z "${KEY_FILE_PASSWORD}" && -n "${CERT_PASSPHRASE}" ]]; then
    KEY_FILE_PASSWORD="${CERT_PASSPHRASE}"
fi

if [[ "${SSL}" == "true" ]]; then
    echo "🔐  Configuring TLS/SSL..."

    if [[ ! -f "${CERT_FILE}" ]]; then
        echo "❌  FATAL: SSL certificate file not found: ${CERT_FILE}"
        exit 1
    fi

    if [[ ! -f "${KEY_FILE}" ]]; then
        echo "❌  FATAL: SSL private key file not found: ${KEY_FILE}"
        exit 1
    fi

    echo "✓  TLS enabled - using:"
    echo "   Certificate: ${CERT_FILE}"
    echo "   Private Key: ${KEY_FILE}"
else
    echo "🔓  Running without TLS (HTTP only)"
fi

#────────────────────────────────────────────────────────────────────────────────
# SECTION 8: Verify Granian Installation
#────────────────────────────────────────────────────────────────────────────────
if ! command -v granian &> /dev/null; then
    echo "❌  FATAL: granian command not found!"
    echo "   Please install it with: pip install 'mcpgateway[granian]'"
    echo "   Or: pip install granian"
    exit 1
fi

GRANIAN_VERSION=$(granian --version 2>&1 || echo "unknown")
echo "✓  Granian found: $(command -v granian) (${GRANIAN_VERSION})"

#────────────────────────────────────────────────────────────────────────────────
# SECTION 9: Launch Granian Server
#────────────────────────────────────────────────────────────────────────────────
echo "🚀  Starting Granian server..."
echo "─────────────────────────────────────────────────────────────────────"

# Build command array with performance-optimized settings
cmd=(
    granian
    --interface asgi
    --host "${HOST}"
    --port "${PORT}"
    --workers "${GRANIAN_WORKERS}"
    --runtime-mode "${GRANIAN_RUNTIME_MODE}"
    --runtime-threads "${GRANIAN_RUNTIME_THREADS}"
    --blocking-threads "${GRANIAN_BLOCKING_THREADS}"
    --loop "${GRANIAN_LOOP}"
    --task-impl "${GRANIAN_TASK_IMPL}"
    --http "${GRANIAN_HTTP}"
    --http1-buffer-size "${GRANIAN_HTTP1_BUFFER_SIZE}"
    --backlog "${GRANIAN_BACKLOG}"
    --backpressure "${GRANIAN_BACKPRESSURE}"
    --log-level "${GRANIAN_LOG_LEVEL}"
)

# HTTP/1 pipeline flush (experimental - aggregates flushes for pipelined responses)
if [[ "${GRANIAN_HTTP1_PIPELINE_FLUSH}" == "true" ]]; then
    cmd+=( --http1-pipeline-flush )
fi

# Process naming (optional - requires granian[pname] and setproctitle)
if "${PYTHON}" -c "import setproctitle" 2>/dev/null; then
    cmd+=( --process-name "mcpgateway" )
    echo "✓  Process naming enabled"
fi

# WebSocket support (enabled by default)
cmd+=( --ws )

# Respawn failed workers (recommended for production)
if [[ "${GRANIAN_RESPAWN_FAILED}" == "true" ]]; then
    cmd+=( --respawn-failed-workers )
fi

# Access logging
DISABLE_ACCESS_LOG=${DISABLE_ACCESS_LOG:-true}
if [[ "${DISABLE_ACCESS_LOG}" == "true" ]]; then
    cmd+=( --no-access-log )
    echo "🚫  Access logging disabled for performance"
else
    cmd+=( --access-log )
fi

# Developer mode with hot reload (requires granian[reload])
if [[ "${GRANIAN_DEV_MODE}" == "true" ]]; then
    cmd+=( --reload )
    echo "🔧  Developer mode enabled - hot reload active"

    # In dev mode, reduce workers for better debugging
    if [[ "${GRANIAN_WORKERS}" -gt 2 ]]; then
        echo "   Reducing workers to 2 for developer mode (was ${GRANIAN_WORKERS})"
        # Update workers in command array
        for i in "${!cmd[@]}"; do
            if [[ "${cmd[$i]}" == "--workers" ]]; then
                cmd[$((i+1))]=2
                break
            fi
        done
    fi
fi

# SSL/TLS configuration
if [[ "${SSL}" == "true" ]]; then
    cmd+=( --ssl-certificate "${CERT_FILE}" --ssl-keyfile "${KEY_FILE}" )
    if [[ -n "${KEY_FILE_PASSWORD:-}" ]]; then
        cmd+=( --ssl-keyfile-password "${KEY_FILE_PASSWORD}" )
    fi
fi

# Add the application module (Granian uses module:app format)
cmd+=( "mcpgateway.main:app" )

# Display final command for debugging
echo "📋  Command: ${cmd[*]}"
echo "─────────────────────────────────────────────────────────────────────"

# Launch Granian
trap - EXIT
exec "${cmd[@]}"
