#!/bin/bash
# ==============================================================================================
# ================================= Ramdisk Setup ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Creates the tmpfs ramdisk, SSD fallback directory, transcode symlink, and
# pre-creates transcoding-temp on the ramdisk. Run once at array start via
# array_started.sh (System_Essentials/). Idempotent — already-mounted ramdisk
# reports status and exits cleanly. Always resets the symlink to the ramdisk
# on boot, ensuring a clean state regardless of what state it was in before
# shutdown.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Creates four things in order:
#   1. RAMDISK_PATH  — tmpfs mount (size: HOST*_RAMDISK_SIZE ceiling, not a reservation)
#   2. TRANSCODE_SSD — SSD fallback directory and transcoding-temp inside it
#   3. TRANSCODE_LINK — symlink reset to RAMDISK_PATH (clean state at every boot)
#   4. transcoding-temp/ inside RAMDISK_PATH — pre-created before Emby starts
#
# The transcoding-temp pre-creation is critical: if it doesn't exist on the ramdisk
# when Emby starts, Emby searches all accessible paths for an existing one and finds
# the SSD fallback version — routing all sessions there until Emby restarts.
#
# Initialises /tmp/transcode_state.db with current target and flip counters.
# /tmp resets on reboot — correct, transcode state should not persist across boots.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Size Is a Ceiling, Not a Reservation
#   tmpfs allocates on write. RAMDISK_SIZE caps how large the ramdisk may grow; it does
#   not take that RAM away from the system up front. Sizing it generously costs nothing
#   until transcodes actually fill it, which is why the ceiling can sit well above
#   normal usage without starving anything.
#
# Clean Symlink State Every Boot
#   TRANSCODE_LINK is reset to the ramdisk at every array start rather than left wherever
#   the last flip put it. transcode_manager.sh flips it to SSD under pressure, and that
#   flip is a runtime response to a full ramdisk — carrying it across a reboot would mean
#   starting on the fallback with an empty ramdisk sitting unused.
#
# Pre-Create Before Emby Starts
#   transcoding-temp/ is created on the ramdisk before any container launches. Emby
#   searches accessible paths for an existing transcoding-temp at startup and binds to
#   the first it finds — if only the SSD copy exists, every session lands there until
#   Emby is restarted. Ordering here is not cosmetic; it decides where transcodes go.
#
# Idempotent Re-Runs
#   An already-mounted ramdisk is left mounted and only the symlink and permissions are
#   verified. Re-running never tears down a mount that active sessions are writing into.
#
# State Belongs in /tmp
#   The transcode state DB lives in /tmp and resets on reboot. Flip counters and the
#   current target describe a running system; carrying them across a boot would make the
#   manager act on pressure that no longer exists.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   mount and symlink creation require root.
#
# Single Instance Lock
#   acquire_lock prevents duplicate runs at array start.
#
# Idempotent Mount Check
#   If RAMDISK_PATH is already a mountpoint, reports status and exits cleanly
#   without attempting to remount or changing anything.
#
# Silent on Success
#   Startup script runs on every boot — no output when healthy.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# host*.conf
#
#   HOST*_RAMDISK_SIZE
#       tmpfs ceiling (e.g. 10G). Must change together with WARN_GB and LOW_GB.
#       Aliased by detect_hosts() → RAMDISK_SIZE.
#
#   HOST*_RAMDISK_WARN_GB
#       Usage level at which transcode_manager.sh flips symlink to SSD.
#
#   HOST*_RAMDISK_LOW_GB
#       Usage level at which transcode_manager.sh flips back to ramdisk.
#
#   HOST*_TRANSCODE_SSD
#       SSD fallback directory path.
#       Aliased by detect_hosts() → TRANSCODE_SSD.
#
# master.conf
#
#   TRANSCODE_LINK
#       Symlink path Emby uses as its transcode directory. Must match the path
#       configured in Emby's transcoding settings.
#
#   TRANSCODE_CHMOD / TRANSCODE_OWNER
#       Permissions applied to both ramdisk and SSD directories. (default: 755 / nobody:users)
#
#   TRANSCODE_STATE_FILE
#       Override state file path. (default: ${STATE_DIR}/transcode_state.db)
#
# ==============================================================================================
# STATE FILES
# ==============================================================================================
#
#   TRANSCODE_STATE_FILE (default: ${STATE_DIR}/transcode_state.db)
#       Current symlink target + flip count tracking. Lives in STATE_DIR
#       (ephemeral on Unraid — resets on reboot correctly).
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# ramdisk_setup.sh
#     Normal setup run. Called by array_started.sh at boot.
#
# ramdisk_setup.sh --dry-run
#     Show what would be created without creating anything.
#
# ramdisk_setup.sh --status
#     Show current ramdisk mount state, symlink target, and SSD directory state.
#
# ramdisk_setup.sh --log
#     Verbose output showing each creation step.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../load_config.sh"

parse_args "$@"

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root — mount and symlink require root"
    exit 1
fi


acquire_lock

# detect_hosts() sets MY_ID and aliases RAMDISK_SIZE, TRANSCODE_SSD etc.
detect_hosts

# This script mounts a tmpfs over RAMDISK_PATH and, when TRANSCODE_LINK exists but is not a
# symlink, rm -rf's it before replacing it. Neither of those checks catches a collapsed path:
# / and /mnt both satisfy -e, and mounting a tmpfs over a system directory hides its contents
# for the life of the mount. Require at least two path components before either is touched.
for _tc_pair in "RAMDISK_PATH:$RAMDISK_PATH" "TRANSCODE_LINK:$TRANSCODE_LINK"; do
    _tc_name="${_tc_pair%%:*}"
    _tc_path="${_tc_pair#*:}"
    _tc_slashes="${_tc_path//[^\/]/}"
    if [[ -z "$_tc_path" || "$_tc_path" != /* || "${#_tc_slashes}" -lt 2 ]]; then
        error "$_tc_name is unset or unsafe ('${_tc_path:-unset}') — refusing to mount or relink"
        notify "Ramdisk setup aborted on $(hostname) ($MY_ID) — $_tc_name is '${_tc_path:-unset}'" \
            "Ramdisk Setup" "warning"
        exit 1
    fi
done
unset _tc_pair _tc_name _tc_path _tc_slashes

log "Identity:  $MY_ID ($LOCAL_SERVER_NAME)"
log "Ramdisk:   $RAMDISK_PATH ($RAMDISK_SIZE)"
log "Fallback:  $TRANSCODE_SSD"
log "Symlink:   $TRANSCODE_LINK"

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
    echo "$ICON_HOST   Identity:      $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_RAM    Ramdisk path:  $RAMDISK_PATH"
    echo "$ICON_RAM    Ramdisk size:  $RAMDISK_SIZE"
    echo "$ICON_RAM    Warn at:       ${RAMDISK_WARN_GB}GB"
    echo "$ICON_RAM    Flip at:       ${RAMDISK_LOW_GB}GB"
    echo "$ICON_DISK   SSD fallback:  $TRANSCODE_SSD"
    echo "$ICON_LINK   Symlink:       $TRANSCODE_LINK"
    echo "$ICON_GEAR   Owner:         $TRANSCODE_OWNER"
    echo "$ICON_GEAR   Mode:          $TRANSCODE_CHMOD"
    echo ""

    if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
        USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
        AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
        echo "  $ICON_RAM  Ramdisk: mounted — $USAGE used / $AVAIL available ✅"
    else
        echo "  $ICON_RAM  Ramdisk: NOT mounted"
    fi

    if [[ -L "$TRANSCODE_LINK" ]]; then
        TARGET=$(readlink "$TRANSCODE_LINK")
        echo "  $ICON_LINK Symlink: $TRANSCODE_LINK → $TARGET"
    else
        echo "  $ICON_LINK Symlink: not set"
    fi

    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

# ==============================================================================================
# ━━━ Ramdisk ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_RAM Ramdisk — $MY_ID ━━━"
log "$ICON_RAM  Path: $RAMDISK_PATH"
log "$ICON_RAM  Size: $RAMDISK_SIZE"
echo ""

START=$(date +%s)
SETUP_SUCCESS=true

if mountpoint -q "$RAMDISK_PATH" 2>/dev/null; then
    USAGE=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $3}')
    AVAIL=$(df -BG "$RAMDISK_PATH" | awk 'NR==2 {print $4}')
    log "Ramdisk already mounted — $USAGE used / $AVAIL available"
    log "Skipping mount — verifying symlink and permissions"
else
    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would create $RAMDISK_PATH"
        warn "DRY RUN — would mount tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH"
    else
        log "Creating ramdisk mount point: $RAMDISK_PATH"
        mkdir -p "$RAMDISK_PATH" || {
            error "Failed to create $RAMDISK_PATH"
            notify "Ramdisk setup failed on $(hostname) ($MY_ID) — could not create mount point" \
                "Ramdisk Setup" "warning"
            exit 1
        }

        log "Mounting tmpfs ${RAMDISK_SIZE} at $RAMDISK_PATH..."
        if mount -t tmpfs -o size="$RAMDISK_SIZE" tmpfs "$RAMDISK_PATH"; then
            warn "Ramdisk mounted — ${RAMDISK_SIZE} at $RAMDISK_PATH ✅"
        else
            error "Failed to mount ramdisk at $RAMDISK_PATH"
            notify "Ramdisk setup failed on $(hostname) ($MY_ID) — mount failed" \
                "Ramdisk Setup" "warning"
            exit 1
        fi
    fi
fi

# ==============================================================================================
# ━━━ SSD Fallback ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_DISK SSD Fallback ━━━"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would create SSD fallback: $TRANSCODE_SSD"
else
    if [[ -d "$TRANSCODE_SSD" ]]; then
        log "SSD fallback already exists: $TRANSCODE_SSD"
    else
        log "Creating SSD fallback directory: $TRANSCODE_SSD"
        if mkdir -p "$TRANSCODE_SSD"; then
            echo "SSD fallback created: $TRANSCODE_SSD ✅"
        else
            error "Failed to create SSD fallback: $TRANSCODE_SSD"
            SETUP_SUCCESS=false
        fi
    fi
fi

# ==============================================================================================
# ━━━ Symlink ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_LINK Symlink ━━━"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would set $TRANSCODE_LINK → $RAMDISK_PATH"
else
    if [[ -L "$TRANSCODE_LINK" ]]; then
        CURRENT_TARGET=$(readlink "$TRANSCODE_LINK")
        if [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
            log "Symlink already points to ramdisk — no change needed ✅"
        else
            log "Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH"
            ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
                error "Failed to update symlink"
                SETUP_SUCCESS=false
            }
        fi
    elif [[ -e "$TRANSCODE_LINK" ]]; then
        warn "$TRANSCODE_LINK exists but is not a symlink — removing and replacing"
        rm -rf "$TRANSCODE_LINK"
        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
            error "Failed to create symlink"
            SETUP_SUCCESS=false
        }
    else
        log "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH"
        mkdir -p "$(dirname "$TRANSCODE_LINK")"
        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
            error "Failed to create symlink"
            SETUP_SUCCESS=false
        }
    fi

    [[ "$SETUP_SUCCESS" == true ]] && echo "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"
fi

# ==============================================================================================
# ━━━ Transcoding-temp Directory ━━━
# ==============================================================================================
# Pre-created inside ramdisk so Emby always finds it there at session start.
# Without this Emby creates it at its own first-writable path — which may be
# SSD even when the symlink points at the ramdisk — locking all sessions onto SSD.
echo ""
echo "━━━ $ICON_GEAR Transcoding Temp Directory ━━━"

TRANSCODE_TEMP_DIR="${RAMDISK_PATH}/transcoding-temp"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would create $TRANSCODE_TEMP_DIR"
else
    if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
        log "transcoding-temp already exists on ramdisk"
    else
        if mkdir -p "$TRANSCODE_TEMP_DIR"; then
            echo "Created transcoding-temp on ramdisk ✅"
        else
            error "Failed to create transcoding-temp on ramdisk"
            SETUP_SUCCESS=false
        fi
    fi

    if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
        chmod "$TRANSCODE_CHMOD" "$TRANSCODE_TEMP_DIR"
        chown "$TRANSCODE_OWNER" "$TRANSCODE_TEMP_DIR"
        log "Permissions set on transcoding-temp ($TRANSCODE_CHMOD $TRANSCODE_OWNER)"
    fi
fi

# ==============================================================================================
# ━━━ Permissions ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_GEAR Permissions ━━━"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would apply $TRANSCODE_CHMOD $TRANSCODE_OWNER to $RAMDISK_PATH and $TRANSCODE_SSD"
else
    for path in "$RAMDISK_PATH" "$TRANSCODE_SSD"; do
        if [[ -d "$path" ]]; then
            chmod "$TRANSCODE_CHMOD" "$path"
            chown "$TRANSCODE_OWNER" "$path"
            log "Permissions set: $path ($TRANSCODE_CHMOD $TRANSCODE_OWNER)"
        fi
    done
fi

# ==============================================================================================
# ━━━ Initialise State File ━━━
# ==============================================================================================
if [[ "$DRY_RUN" == false ]]; then
    STATE_FILE="${TRANSCODE_STATE_FILE:-${STATE_DIR:-/tmp}/transcode_state.db}"
    NOW=$(date +%s)
    cat > "$STATE_FILE" <<EOF
current_target=$RAMDISK_PATH
last_flip_time=$NOW
flip_count_hour=0
flip_hour_start=$NOW
EOF
    log "State file initialised: $STATE_FILE"
fi

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY RAMDISK SETUP SUMMARY ━━━━━"
echo "$ICON_HOST  Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_RAM   Ramdisk:  $RAMDISK_PATH ($RAMDISK_SIZE)"
echo "$ICON_DISK  Fallback: $TRANSCODE_SSD"
echo "$ICON_LINK  Symlink:  $TRANSCODE_LINK"
echo "$ICON_TIME  Duration: $(format_duration $(( END - START )))"
echo ""

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no changes made"
elif [[ "$SETUP_SUCCESS" == true ]]; then
    echo "$ICON_DONE Status: done ✅"
else
    echo "$ICON_ERROR Status: SETUP HAD ERRORS"
    notify "Ramdisk setup errors on $(hostname) ($MY_ID) — check output" \
        "Ramdisk Setup" "warning"
    exit 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"