#!/usr/bin/env bash
# TODO:
# - clear untracked pid files in run/supervisor/*.pid

# This script starts/stops/restarts service(s) associated with the project.

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
    SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
    SOURCE="$(readlink "$SOURCE")"
    [[ $SOURCE != /* ]] && SOURCE="$SCRIPT_DIR/$SOURCE"
done
REAL_SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

if [[ "$REAL_SCRIPT_DIR" == *"/vendor/"* ]]; then
    # Installed as a vendor package: <root>/vendor/gda/supervisor-bundle/bin
    PROJECT_ROOT=$(realpath "$REAL_SCRIPT_DIR/../../../../")
elif [[ "$REAL_SCRIPT_DIR" == *"/bin"* ]]; then
    # Placed directly in project's bin/ directory
    PROJECT_ROOT=$(realpath "$REAL_SCRIPT_DIR/..")
else
    # Fallback default
    PROJECT_ROOT=$(pwd)
fi

SUPERVISOR_CONFIG_DIR_PATH="$PROJECT_ROOT/etc/supervisor/conf.d"
SUPERVISOR_RUN_DIR="$PROJECT_ROOT/run/supervisor"
SUPERVISORD_PATH=$(command -v supervisord)

DEFAULT_LOG_DIR_BASIS="$PROJECT_ROOT/var/log"
SUPERVISOR_DEFAULT_LOG_DIR="$DEFAULT_LOG_DIR_BASIS/supervisor"

get_pid_file_path() {
    local CONFIG_FILE="$1"
    local CONFIG_DIR_PATH
    local PID_FILE_PATH

    if [ -z "$CONFIG_FILE" ]; then
        echo "ERROR: get_pid_file needs 1 argument: config file path" >&2
        return 1
    fi

    CONFIG_DIR_PATH=$(dirname "$CONFIG_FILE")
    PID_FILE_PATH=$(grep -i '^pidfile' "$CONFIG_FILE" | cut -d'=' -f2 | tr -d ' "')

    if [ -z "$PID_FILE_PATH" ]; then
        return 1
    fi

    # Handle %(here)s variable or relative paths
    PID_FILE_PATH=${PID_FILE_PATH//\%\(here\)s/$CONFIG_DIR_PATH}
    if [[ "$PID_FILE_PATH" != /* ]]; then
        PID_FILE_PATH="$CONFIG_DIR_PATH/$PID_FILE_PATH"
    fi

    echo "$PID_FILE_PATH"
    return 0
}

resolve_config_file_pid() {
    if [ -z "$1" ] || [ ! -f "$1" ]; then
        return 1
    fi

    cat "$1" 2>/dev/null
    return 0
}

check_if_pid_is_running() {
    local PID="$1"
    if [ -z "$PID" ] || ! [[ "$PID" =~ ^[0-9]+$ ]]; then
        return 1
    fi

    kill -0 "$PID" 2>/dev/null
}

waiting_process_kill() {
    local PID="$1"
    local MAX_TRIES=${2:-30}
    local SLEEP_TIME=${3:-1}
    local TRIES=0

    while kill -0 "$PID" 2>/dev/null; do
        if (( TRIES >= MAX_TRIES )); then
            echo "WARNING: Process $PID did not terminate after $MAX_TRIES seconds" >&2
            return 1
        fi

        ((TRIES++))
        sleep "$SLEEP_TIME"
    done

    return 0
}

kill_process_sync() {
    local PID="$1"
    if check_if_pid_is_running "$PID"; then
        kill -15 "$PID" 2>/dev/null
        waiting_process_kill "$PID"
        return 0
    fi

    return 1
}

start() {
    echo "Starting services from $SUPERVISOR_CONFIG_DIR_PATH..."

    if [ ! -d "$SUPERVISOR_CONFIG_DIR_PATH" ]; then
        echo "ERROR: Config directory not found: $SUPERVISOR_CONFIG_DIR_PATH" >&2
        return 1
    fi

    local FOUND_CONFIGS=0

    for CONFIG_FILENAME in "$SUPERVISOR_CONFIG_DIR_PATH"/*.conf; do
        [ -e "$CONFIG_FILENAME" ] || continue
        FOUND_CONFIGS=1

        PID_FILE_PATH=$(get_pid_file_path "$CONFIG_FILENAME")

        if [ -n "$PID_FILE_PATH" ] && [ -f "$PID_FILE_PATH" ]; then
            PROCESS_PID=$(resolve_config_file_pid "$PID_FILE_PATH")
            if check_if_pid_is_running "$PROCESS_PID"; then
                echo "WARNING: Service \"$(basename "$CONFIG_FILENAME")\" is already running (PID: $PROCESS_PID), skipped"
                continue
            fi
        fi

        if "$SUPERVISORD_PATH" -c "$CONFIG_FILENAME"; then
            sleep 1
            PROCESS_PID=$(resolve_config_file_pid "$PID_FILE_PATH")
            echo "INFO: Started service \"$(basename "$CONFIG_FILENAME")\" (PID: ${PROCESS_PID:-unknown})"
        else
            echo "ERROR: Unable to start supervisor with config \"$CONFIG_FILENAME\"" >&2
        fi
    done

    if [ "$FOUND_CONFIGS" -eq 0 ]; then
        echo "INFO: No .conf files found in $SUPERVISOR_CONFIG_DIR_PATH"
    fi

    return 0
}

stop() {
    echo "Stopping services..."

    if [ ! -d "$SUPERVISOR_CONFIG_DIR_PATH" ]; then
        return 0
    fi

    for CONFIG_FILENAME in "$SUPERVISOR_CONFIG_DIR_PATH"/*.conf; do
        [ -e "$CONFIG_FILENAME" ] || continue

        PID_FILE_PATH=$(get_pid_file_path "$CONFIG_FILENAME")

        if [ -z "$PID_FILE_PATH" ] || [ ! -f "$PID_FILE_PATH" ]; then
            echo "INFO: Service \"$(basename "$CONFIG_FILENAME")\" does not seem to be running, skipped"
            continue
        fi

        PROCESS_PID=$(resolve_config_file_pid "$PID_FILE_PATH")
        if check_if_pid_is_running "$PROCESS_PID"; then
            if kill_process_sync "$PROCESS_PID"; then
                echo "INFO: Stopped PID $PROCESS_PID for config \"$(basename "$CONFIG_FILENAME")\""
            else
                echo "ERROR: PID $PROCESS_PID was not stopped properly" >&2
            fi
        fi
    done

    return 0
}

restart() {
    stop
    sleep 1
    start
    return 0
}

# -- EXECUTION -- #

if [ ! -x "$SUPERVISORD_PATH" ]; then
    echo "ERROR: supervisord executable not found on system. Please install supervisor." >&2
    exit 1
fi

if [ ! -d "$SUPERVISOR_RUN_DIR" ]; then
    mkdir -p "$SUPERVISOR_RUN_DIR"
fi

if [ -d "$DEFAULT_LOG_DIR_BASIS" ] && [ ! -d "$SUPERVISOR_DEFAULT_LOG_DIR" ]; then
    mkdir -p "$SUPERVISOR_DEFAULT_LOG_DIR"
fi

if [ $# -eq 0 ]; then
    echo "ERROR: Missing argument. Usage: vendor/bin/supervisor {start|stop|restart}" >&2
    exit 1
fi

ACTION="$1"

case "$ACTION" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    *)
        echo "ERROR: Invalid action '$ACTION'. Use 'start', 'stop' or 'restart'." >&2
        exit 1
        ;;
esac

exit $?
