#!/bin/bash

###################################
# Configuration
###################################

# Where your Tdarr Node log is located
TDARR_NODE_LOG_PATH="/mnt/user/appdata/n1/logs/Tdarr_Node_Log.txt"

# ------------- T1 (Required) -------------
T1_TAUTULLI_API_KEY="ebdb8c80fc2b461ea182243dbc1b27a1"
T1_TAUTULLI_URL="http://10.0.0.10:8181/api/v2"

####################################### IMPORTANT NOTE #######################################
# Additional Tautulli configurations are only needed if you run multiple Plex servers
# on the same GPU (e.g., a primary and a backup Plex instance). This lets the script
# track transcodes across all your servers sharing that single GPU.
#
# In most cases (99% of setups), you only have one Plex server (per gpu), so you can ignore
# any extra Tautulli entries.

# ------------- T2 (Optional) -------------
T2_TAUTULLI_API_KEY=""
T2_TAUTULLI_URL=""

# ------------- T3 (Optional) -------------
T3_TAUTULLI_API_KEY=""
T3_TAUTULLI_URL=""

# ------------- T4 (Optional) -------------
T4_TAUTULLI_API_KEY=""
T4_TAUTULLI_URL=""
###############################################################################################

# ----------- Tdarr Settings -------------
TDARR_ALTER_WORKERS=true       # If true, we adjust GPU workers; otherwise we kill container on threshold
TDARR_DEFAULT_LIMIT=3          # Default GPU workers when watchers=0
TDARR_API_URL="http://10.0.0.10:8265"   # WITHOUT /api/v2
CONTAINER_NAME="N1"            # Name of your Tdarr Node Docker container

# ------------ IF >>> TDARR_ALTER_WORKERS=true 
# We only start reducing workers when watchers >= OFFSET_THRESHOLD.
OFFSET_THRESHOLD=2      # For Tdarr Scaling, number of transcodes reached to start reducing GPU Workers by 1 for each >= OFFSET_THRESHOLD

# ------------ IF >>> TDARR_ALTER_WORKERS=false
TRANSCODE_THRESHOLD=4   # For Tdarr Killer, number of transcodes reached to kill the tdarr node (when TDARR_ALTER_WORKERS=false)

# ----------- Other -------------
WAIT_SECONDS=10                # Sleep after adjustments
BASIC_CHECK=3                  # Poll interval (seconds) when idle

###################################
# End of configuration
###################################

# Simple logging function with timestamp
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

# ------------------------------------------------------------
# Function: find_latest_node_id
# ------------------------------------------------------------
find_latest_node_id() {
    if [ -f "$TDARR_NODE_LOG_PATH" ]; then
        local found
        found=$(grep -oP '"nodeID":\s*"\K[^"]+' "$TDARR_NODE_LOG_PATH" | tail -n1)
        echo "$found"
    else
        echo ""
    fi
}

# Initialize global variable
TDARR_NODE_ID=""

# ------------------------------------------------------------
# Function: ensure_node_id_loaded
# ------------------------------------------------------------
ensure_node_id_loaded() {
    if [ -n "$TDARR_NODE_ID" ]; then
        return 0
    fi

    log_message "Attempting to retrieve nodeID from $TDARR_NODE_LOG_PATH"
    local found
    found=$(find_latest_node_id)

    if [ -z "$found" ]; then
        log_message "ERROR: Could not find any nodeID in $TDARR_NODE_LOG_PATH."
        return 1
    fi

    TDARR_NODE_ID="$found"
    log_message "Found nodeID: $TDARR_NODE_ID"
    return 0
}

# ------------------------------------------------------------
# Function: refresh_node_id_if_changed
# ------------------------------------------------------------
refresh_node_id_if_changed() {
    local latest
    latest=$(find_latest_node_id)
    if [ -z "$latest" ]; then
        log_message "WARNING: Could not find any 'nodeID' lines in the log to refresh."
        return
    fi

    if [ "$latest" != "$TDARR_NODE_ID" ]; then
        log_message "NOTICE: nodeID changed from [$TDARR_NODE_ID] -> [$latest]. Updating."
        TDARR_NODE_ID="$latest"
    else
        log_message "NOTICE: nodeID is still the same [$TDARR_NODE_ID]."
    fi
}

# ------------------------------------------------------------
# Function: check_single_tautulli_connection
# ------------------------------------------------------------
check_single_tautulli_connection() {
    local api_key="$1"
    local url="$2"
    if [ -z "$api_key" ] || [ -z "$url" ]; then
        return 2
    fi

    log_message "Checking Tautulli at: $url"
    local response
    response=$(curl -s "${url}?apikey=${api_key}&cmd=get_activity")

    if echo "$response" | jq . >/dev/null 2>&1; then
        log_message "Tautulli OK: $url"
        return 0
    else
        log_message "WARNING: Could not connect or invalid JSON: $url"
        return 1
    fi
}

# ------------------------------------------------------------
# Function: check_tautulli_connections_on_startup
# ------------------------------------------------------------
check_tautulli_connections_on_startup() {
    # T1 must work
    check_single_tautulli_connection "$T1_TAUTULLI_API_KEY" "$T1_TAUTULLI_URL"
    if [ $? -ne 0 ]; then
        log_message "ERROR: T1 not reachable. Exiting."
        exit 1
    fi

    # T2..T4 are optional
    check_single_tautulli_connection "$T2_TAUTULLI_API_KEY" "$T2_TAUTULLI_URL" || true
    check_single_tautulli_connection "$T3_TAUTULLI_API_KEY" "$T3_TAUTULLI_URL" || true
    check_single_tautulli_connection "$T4_TAUTULLI_API_KEY" "$T4_TAUTULLI_URL" || true
}

# ------------------------------------------------------------
# Function: fetch_transcode_counts_from_tautulli
# ------------------------------------------------------------
fetch_transcode_counts_from_tautulli() {
    local api_key="$1"
    local url="$2"

    if [ -z "$api_key" ] || [ -z "$url" ]; then
        echo "0 0"
        return
    fi

    local resp
    resp=$(curl -s "${url}?apikey=${api_key}&cmd=get_activity")
    if ! echo "$resp" | jq . >/dev/null 2>&1; then
        echo "0 0"
        return
    fi

    local local_cnt remote_cnt
    # Only count sessions that are transcoding video, not just audio
    local_cnt=$(echo "$resp" | jq '[.response.data.sessions[]?
       | select(.transcode_decision == "transcode" and .video_decision == "transcode" and (.ip_address | startswith("10.0.0.")))] | length')
    remote_cnt=$(echo "$resp" | jq '[.response.data.sessions[]?
       | select(.transcode_decision == "transcode" and .video_decision == "transcode" and (.ip_address | startswith("10.0.0.") | not))] | length')

    echo "$local_cnt $remote_cnt"
}

# ------------------------------------------------------------
# Global watchers total
# ------------------------------------------------------------
total_count=0

# ------------------------------------------------------------
# Function: is_plex_transcoding_over_threshold
# ------------------------------------------------------------
is_plex_transcoding_over_threshold() {
    log_message "Checking Plex transcodes..."

    local total_local=0
    local total_remote=0

    # T1
    read t1_local t1_remote < <(fetch_transcode_counts_from_tautulli "$T1_TAUTULLI_API_KEY" "$T1_TAUTULLI_URL")
    total_local=$(( total_local + t1_local ))
    total_remote=$(( total_remote + t1_remote ))

    # T2
    read t2_local t2_remote < <(fetch_transcode_counts_from_tautulli "$T2_TAUTULLI_API_KEY" "$T2_TAUTULLI_URL")
    total_local=$(( total_local + t2_local ))
    total_remote=$(( total_remote + t2_remote ))

    # T3
    read t3_local t3_remote < <(fetch_transcode_counts_from_tautulli "$T3_TAUTULLI_API_KEY" "$T3_TAUTULLI_URL")
    total_local=$(( total_local + t3_local ))
    total_remote=$(( total_remote + t3_remote ))

    # T4
    read t4_local t4_remote < <(fetch_transcode_counts_from_tautulli "$T4_TAUTULLI_API_KEY" "$T4_TAUTULLI_URL")
    total_local=$(( total_local + t4_local ))
    total_remote=$(( total_remote + t4_remote ))

    total_count=$(( total_local + total_remote ))

    log_message "Found $total_local local & $total_remote remote => total=$total_count, threshold=$TRANSCODE_THRESHOLD"

    # Return 0 if watchers >= threshold
    if [ "$total_count" -ge "$TRANSCODE_THRESHOLD" ]; then
        return 0
    fi
    return 1
}

# ------------------------------------------------------------
# Function: is_container_running
# ------------------------------------------------------------
is_container_running() {
    local s
    s=$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null)
    [ "$s" = "true" ]
}

# ------------------------------------------------------------
# Function: adjust_tdarr_workers
# 
# We only start subtracting from TDARR_DEFAULT_LIMIT once watchers >= OFFSET_THRESHOLD.
# Example with OFFSET_THRESHOLD=3:
#   watchers=2 => no reduce
#   watchers=3 => reduce by 1
#   watchers=4 => reduce by 2, etc.
# ------------------------------------------------------------
adjust_tdarr_workers() {
    ensure_node_id_loaded || return

    local watchers="$1"

    # 1) Calculate how many watchers are above the offset
    #    If OFFSET_THRESHOLD=0, use watchers directly
    #    If OFFSET_THRESHOLD>0 and watchers < OFFSET_THRESHOLD => watchersOverOffset=0
    #    If OFFSET_THRESHOLD>0 and watchers=3 => watchersOverOffset=1 => reduce by 1
    #    If OFFSET_THRESHOLD>0 and watchers=4 => watchersOverOffset=2 => reduce by 2, etc.
    local watchersOverOffset
    if [ "$OFFSET_THRESHOLD" -eq 0 ]; then
        # When offset is 0, reduce by exactly the watcher count
        watchersOverOffset=$watchers
    else
        # When offset >0, start reducing only after reaching threshold
        watchersOverOffset=$(( watchers - OFFSET_THRESHOLD + 1 ))
        if [ "$watchersOverOffset" -lt 0 ]; then
            watchersOverOffset=0
        fi
    fi

    # 2) Desired = TDARR_DEFAULT_LIMIT - watchersOverOffset
    local desired=$(( TDARR_DEFAULT_LIMIT - watchersOverOffset ))
    if [ "$desired" -lt 0 ]; then
        desired=0
    fi

    log_message "watchers=$watchers => watchersOverOffset=$watchersOverOffset => desiredWorkers=$desired"

    # poll-worker-limits
    local poll_resp
    poll_resp=$(curl -s -X POST "${TDARR_API_URL}/api/v2/poll-worker-limits" \
        -H "Content-Type: application/json" \
        -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'"}}')

    local current
    current=$(echo "$poll_resp" | jq '.workerLimits.transcodegpu' 2>/dev/null)
    if [ -z "$current" ] || [ "$current" = "null" ]; then
        log_message "ERROR: Could not retrieve current GPU worker limit for nodeID='$TDARR_NODE_ID'. Will re-check log for a new ID."
        refresh_node_id_if_changed
        return
    fi

    log_message "Current GPU worker limit: $current"

    local diff=$(( desired - current ))
    if [ "$diff" -eq 0 ]; then
        log_message "Already at the desired GPU worker limit ($desired)."
        return
    fi

    local step
    if [ "$diff" -gt 0 ]; then
        step="increase"
        log_message "Need to increase by $diff"
    else
        step="decrease"
        diff=$(( -diff ))
        log_message "Need to decrease by $diff"
    fi

    local i=0
    while [ $i -lt $diff ]; do
        curl -s -X POST "${TDARR_API_URL}/api/v2/alter-worker-limit" \
            -H "Content-Type: application/json" \
            -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'","process":"'"$step"'","workerType":"transcodegpu"}}' \
            >/dev/null 2>&1
        i=$(( i + 1 ))
        sleep 1
    done

    log_message "GPU worker limit adjustment complete."
}

# ------------------------------------------------------------
# Main Script
# ------------------------------------------------------------
ensure_node_id_loaded
check_tautulli_connections_on_startup

# Main loop with protection against duplicate operations
last_operation=""
last_gpu_limit=0
consecutive_duplicates=0

# Set initial GPU workers on startup
if [ "$TDARR_ALTER_WORKERS" = "true" ]; then
    log_message "Setting initial GPU workers to default limit: $TDARR_DEFAULT_LIMIT on startup"
    
    # Ensure we have nodeID before trying to set workers
    ensure_node_id_loaded || {
        log_message "ERROR: Could not get nodeID, can't set initial GPU workers"
        sleep 5  # Wait a bit and continue, will try again in the main loop
    }
    
    if [ -n "$TDARR_NODE_ID" ]; then
        # Get current limit
        current_limit=$(curl -s -X POST "${TDARR_API_URL}/api/v2/poll-worker-limits" \
            -H "Content-Type: application/json" \
            -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'"}}' | \
            jq '.workerLimits.transcodegpu' 2>/dev/null)
            
        if [ -n "$current_limit" ] && [ "$current_limit" != "null" ]; then
            # Calculate how many workers to add/remove
            diff=$(( TDARR_DEFAULT_LIMIT - current_limit ))
            
            if [ "$diff" -ne 0 ]; then
                step=""
                count=0
                
                if [ "$diff" -gt 0 ]; then
                    step="increase"
                    count=$diff
                    log_message "Need to increase by $diff to reach default limit"
                else
                    step="decrease"
                    count=$(( -diff ))
                    log_message "Need to decrease by $(( -diff )) to reach default limit"
                fi
                
                i=0
                while [ $i -lt $count ]; do
                    curl -s -X POST "${TDARR_API_URL}/api/v2/alter-worker-limit" \
                        -H "Content-Type: application/json" \
                        -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'","process":"'"$step"'","workerType":"transcodegpu"}}' \
                        >/dev/null 2>&1
                    i=$(( i + 1 ))
                    sleep 1
                done
                
                log_message "Initial GPU worker limit set to $TDARR_DEFAULT_LIMIT"
            else
                log_message "GPU workers already at desired default limit: $current_limit"
            fi
        else
            log_message "ERROR: Could not get current GPU worker limit"
        fi
    fi
fi

while true; do
    if is_plex_transcoding_over_threshold; then
        # watchers >= threshold
        if [ "$TDARR_ALTER_WORKERS" = "true" ]; then
            # Check if we're doing the same operation repeatedly
            operation="reduce_workers_$total_count"
            
            # Get current limit to check if it changed
            current_limit=$(curl -s -X POST "${TDARR_API_URL}/api/v2/poll-worker-limits" \
                -H "Content-Type: application/json" \
                -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'"}}' | \
                jq '.workerLimits.transcodegpu' 2>/dev/null)
            
            if [ "$operation" = "$last_operation" ] && [ "$current_limit" = "$last_gpu_limit" ]; then
                consecutive_duplicates=$((consecutive_duplicates + 1))
                if [ $consecutive_duplicates -gt 2 ]; then
                    log_message "Skipping duplicate worker adjustment (done $consecutive_duplicates times already)"
                    sleep "$WAIT_SECONDS"
                    continue
                fi
            else
                consecutive_duplicates=0
            fi
            
            last_operation="$operation"
            last_gpu_limit="$current_limit"
            
            log_message "Threshold exceeded. Reducing GPU workers."
            adjust_tdarr_workers "$total_count"
            sleep "$WAIT_SECONDS"
        else
            # kill container
            operation="kill_container"
            
            if [ "$operation" = "$last_operation" ]; then
                consecutive_duplicates=$((consecutive_duplicates + 1))
                if [ $consecutive_duplicates -gt 2 ]; then
                    log_message "Skipping duplicate container management (done $consecutive_duplicates times already)"
                    sleep "$WAIT_SECONDS"
                    continue
                fi
            else
                consecutive_duplicates=0
            fi
            
            last_operation="$operation"
            
            if is_container_running; then
                log_message "Threshold exceeded: Killing $CONTAINER_NAME"
                docker kill "$CONTAINER_NAME"
            else
                log_message "$CONTAINER_NAME is already stopped."
            fi
            sleep "$WAIT_SECONDS"
        fi
    else
        # watchers < threshold
        if [ "$TDARR_ALTER_WORKERS" = "true" ]; then
            # Check if we're doing the same operation repeatedly
            operation="adjust_workers_$total_count"
            
            # Get current limit to check if it changed
            current_limit=$(curl -s -X POST "${TDARR_API_URL}/api/v2/poll-worker-limits" \
                -H "Content-Type: application/json" \
                -d '{"data":{"nodeID":"'"$TDARR_NODE_ID"'"}}' | \
                jq '.workerLimits.transcodegpu' 2>/dev/null)
            
            if [ "$operation" = "$last_operation" ] && [ "$current_limit" = "$last_gpu_limit" ]; then
                consecutive_duplicates=$((consecutive_duplicates + 1))
                if [ $consecutive_duplicates -gt 2 ]; then
                    log_message "Skipping duplicate worker adjustment (done $consecutive_duplicates times already)"
                    sleep "$BASIC_CHECK"
                    continue
                fi
            else
                consecutive_duplicates=0
            fi
            
            last_operation="$operation"
            last_gpu_limit="$current_limit"
            
            adjust_tdarr_workers "$total_count"
        fi

        # Start container if needed
        operation="start_container"
        
        if [ "$operation" = "$last_operation" ] && is_container_running; then
            consecutive_duplicates=$((consecutive_duplicates + 1))
            if [ $consecutive_duplicates -gt 2 ]; then
                log_message "Skipping duplicate container check (done $consecutive_duplicates times already)"
                sleep "$BASIC_CHECK"
                continue
            fi
        else
            consecutive_duplicates=0
        fi
        
        last_operation="$operation"

        if ! is_container_running; then
            log_message "Below threshold -> Starting container $CONTAINER_NAME."
            docker start "$CONTAINER_NAME"
        else
            log_message "Container $CONTAINER_NAME is already running."
        fi

        sleep "$BASIC_CHECK"
    fi
done
