#!/bin/bash
# ==============================================================================================
# ======================= Arr Full Library Rescan =============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Forces a genuine full disk↔database reconciliation for Lidarr/Sonarr/Radarr. Organic
# scans (triggered by new imports, RSS sync, etc.) only touch the files actually involved —
# an artist/series/movie that already has files sitting untouched on disk never gets its
# file-tracking stats refreshed on its own. Confirmed 2026-07-16: Lidarr reported only ~23%
# of its true trackFileCount with no active scan running, for 1,004 of 1,357 artists — files
# verified present and readable on disk the whole time. Every downstream script (cleanup,
# duplicate-artist detection, discovery) trusts these arr stats as source of truth for what's
# on the share, so silent drift like this is exactly what check_tracked_count_floor() exists
# to catch reactively. This job exists to catch it proactively instead of waiting for someone
# to notice a suspiciously low number.
#
# Runs sequentially across all three arrs, never parallel — each is a heavy full-disk walk,
# and running them concurrently would just contend for the same disk I/O for no benefit.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Lidarr, then Sonarr, then Radarr — strictly sequential. Per arr:
#
#   1. Reachability
#        → check_api; unreachable skips this arr only
#
#   2. Already-scanning check
#        → a rescan already active (manual, or another script) means skip rather than
#          stack a second full-disk walk on top of it
#
#   3. Capture the before count
#        → tracked file count read from the arr's own stats
#
#   4. Trigger the rescan command
#        → RescanFolders (Lidarr) / RescanSeries (Sonarr) / RescanMovie (Radarr)
#
#   5. Poll to completion
#        → bounded by ARR_FULL_RESCAN_TIMEOUT
#
#   6. Report the delta
#        → before vs after tracked count, so drift that was corrected is visible
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Proactive, Not Reactive
#   check_tracked_count_floor() catches stat drift reactively, at the moment some other
#   script is about to act on bad numbers. This job exists so that drift is corrected on a
#   schedule instead of being discovered by whichever cleanup happens to trip over it first.
#
# Sequential by Design
#   Each rescan is a full-disk walk. Running three concurrently contends for the same
#   spindles and finishes no sooner, so the arrs are never parallelised — the slowness is
#   accepted deliberately rather than optimised into I/O thrash.
#
# Never Stack a Scan
#   An already-running rescan is left alone rather than duplicated. A second concurrent
#   walk of the same library doubles the I/O cost and returns nothing the first will not.
#
# Per-Arr Isolation
#   One arr being down, slow, or already scanning must never prevent the other two from
#   being reconciled. Partial coverage beats a skipped run.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Kept for consistency across Arrs_Stack/ — this script makes no direct filesystem writes.
#
# Lock Acquisition
#   acquire_lock "wait" — waits for a prior run rather than skipping or colliding. A full
#   rescan across three arrs runs long and is worth queuing behind, not silently dropping.
#
# Host Detection
#   detect_hosts() aliases each arr's URL and API key.
#
# jq Dependency Check
#   Fails fast if jq is missing. Both the before/after tracked counts and the command
#   payload are built with jq — without it the counts read empty and every delta would be
#   reported as if nothing changed.
#
# Reachability Check
#   check_api before touching an arr; unreachable skips that arr only.
#
# Active-Rescan Check
#   Skips triggering a new rescan if one is already active on that arr, so a duplicate
#   full-disk walk is never stacked. See Tools/arr_rescan_monitor.sh for catching that
#   arr's cache up once the pre-existing scan finishes, rather than waiting a week.
#
# Sequential Only
#   Two arrs' rescans never run in parallel.
#
# Per-Arr Isolation
#   One arr failing, timing out, or being skipped never blocks the others.
#
# Timeout Bound
#   ARR_FULL_RESCAN_TIMEOUT caps the wait per arr, so a rescan that never completes cannot
#   hold the weekly window open indefinitely.
#
# Dry Run Support
#   --dry-run reports which arrs would be rescanned and triggers nothing.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#   ARR_FULL_RESCAN_TIMEOUT — seconds to wait per arr (default 3600). A whole-library
#   RescanFolders/RescanSeries/RescanMovie is far heavier than the 600s pre-flight scan
#   timeout used elsewhere — that shorter timeout is sized for a single release, not a
#   full-library walk.
#
# host*.conf
#   HOST1_LIDARR_URL / _API_KEY, HOST1_SONARR_URL / _API_KEY, HOST1_RADARR_URL / _API_KEY
#   — aliased by detect_hosts()
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   arr_full_rescan.sh              — normal run
#   arr_full_rescan.sh --dry-run    — preview which arrs would be rescanned, trigger nothing
#   arr_full_rescan.sh --log        — verbose, per-arr detail
#
# ==============================================================================================

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

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

parse_args "$@"

if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root"
    exit 1
fi

# Both the tracked-count reads and the command payload are built with jq — without it the
# counts read empty and every arr would report a zero delta as if nothing had drifted.
if ! command -v jq >/dev/null 2>&1; then
    error "jq not found — required for JSON parsing"
    notify "Arr full rescan failed on $(hostname) — jq not installed" "Arr Full Rescan" "warning"
    exit 1
fi

acquire_lock "wait"

detect_hosts

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no rescans will be triggered"

declare -A ARR_FULL_RESCAN_COMMAND=(
    [lidarr]="RescanFolders"
    [sonarr]="RescanSeries"
    [radarr]="RescanMovie"
)

RESCANNED=0
SKIPPED=0

for arr in lidarr sonarr radarr; do
    url_var="${arr^^}_URL"; key_var="${arr^^}_API_KEY"
    url="${!url_var:-}"; key="${!key_var:-}"
    ver="v3"; [[ "$arr" == "lidarr" ]] && ver="v1"

    if [[ -z "$url" || -z "$key" ]]; then
        info "${arr^} not configured on $MY_ID — skipping"
        continue
    fi

    check_api "$url" "${arr^}" 10 || {
        warn "${arr^} unreachable — skipping full rescan this run"
        (( SKIPPED++ ))
        continue
    }

    active=$(arr_active_rescan_command "$arr" "$url" "$key" "$ver")
    if [[ -n "$active" ]]; then
        warn "${arr^} already mid-rescan ($active) — skipping, will catch it next scheduled run (run Tools/arr_rescan_monitor.sh ${arr} to refresh its cache as soon as this one finishes instead of waiting)"
        (( SKIPPED++ ))
        continue
    fi

    endpoint="${ARR_LIBRARY_ENDPOINT[$arr]}"
    expr="${ARR_TRACKED_COUNT_EXPR[$arr]}"

    before_json=$(curl -sf --max-time 60 -H "X-Api-Key: $key" "${url}/api/${ver}/${endpoint}" 2>/dev/null)
    before_count=$(echo "$before_json" | jq "$expr" 2>/dev/null)

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would trigger full ${ARR_FULL_RESCAN_COMMAND[$arr]} for ${arr} (currently: ${before_count:-unknown} tracked)"
        continue
    fi

    log "$ICON_GEAR Triggering full ${ARR_FULL_RESCAN_COMMAND[$arr]} for ${arr} (before: ${before_count:-unknown} tracked)"
    payload=$(jq -c -n --arg name "${ARR_FULL_RESCAN_COMMAND[$arr]}" '{name:$name}')
    trigger_and_await_command "$url" "$key" "$ver" "$payload" "${ARR_FULL_RESCAN_TIMEOUT:-3600}" "$arr"

    after_json=$(curl -sf --max-time 60 -H "X-Api-Key: $key" "${url}/api/${ver}/${endpoint}" 2>/dev/null)
    after_count=$(echo "$after_json" | jq "$expr" 2>/dev/null)

    if [[ -n "$after_json" && -n "$after_count" && "$after_count" != "null" ]]; then
        arr_cache_write "$arr" "$after_json"
        log "$ICON_DONE ${arr^} rescan complete — tracked: ${before_count:-?} → ${after_count}"
        (( RESCANNED++ ))

        # A completed full rescan is ground truth — if it's STILL far below the running
        # baseline, that's a real problem (missing disk, permissions, actual data loss),
        # not a stale-cache or mid-scan artifact. Worth a direct heads-up either way.
        count_file_var="${arr^^}_TRACKED_COUNT_FILE"
        min_pct_var="${arr^^}_MIN_TRACKED_PCT"
        count_file="${!count_file_var:-}"
        min_pct="${!min_pct_var:-80}"
        if [[ -n "$count_file" && -f "$count_file" ]]; then
            baseline=$(cat "$count_file" 2>/dev/null || echo 0)
            if [[ "$baseline" -gt 0 ]]; then
                pct=$(awk "BEGIN {printf \"%d\", ($after_count / $baseline) * 100}")
                if [[ "$pct" -lt "$min_pct" ]]; then
                    notify "${arr^} full rescan complete but tracked count still ${pct}% of baseline ($after_count vs $baseline) on $(hostname) — real drop, not a scan artifact, needs a look" \
                        "Arr Full Rescan" "warning"
                else
                    echo "$after_count" > "$count_file"
                fi
            else
                echo "$after_count" > "$count_file"
            fi
        fi
    else
        warn "${arr^} rescan finished but re-fetch failed — cache not updated"
    fi
done

echo ""
echo "━━━━━ $ICON_SUMMARY ARR FULL RESCAN SUMMARY ━━━━━"
echo "$ICON_HOST    Identity:   $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR    Rescanned:  $RESCANNED"
echo "$ICON_SKIP    Skipped:    $SKIPPED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

exit 0
