#!/bin/bash
# ==============================================================================================
# ========================= Arr Profile Enforcer ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Ensures every series/movie in Sonarr and Radarr is on the correct quality
# profile based on its root folder. Safe to re-run — only touches items whose
# current profile is wrong.
#
# RULES
# ─────────────────────────────────────────────────────────────────────────────
# Root folder path contains "kids" OR "anime"  →  kids profile
# All other root folders                        →  default profile
#
# Profile names are looked up by name from the API at runtime, so profile IDs
# do not need to be hardcoded and work across hosts.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Sonarr then Radarr, each independently:
#
#   1. Fetch quality profiles and resolve the configured names to live IDs
#   2. Fetch the library
#   3. Per item, derive the expected profile from its rootFolderPath
#        contains "kids" or "anime" → kids profile
#        anything else              → default profile
#   4. Compare against the item's current profile
#        already correct → skip, no call made
#        wrong           → queue for the bulk editor PUT
#   5. Issue the change and report counts
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Resolve Profile IDs at Runtime
#   Profiles are looked up by name from the API on every run rather than hardcoding IDs.
#   Profile IDs differ between hosts and change when profiles are recreated; a stale
#   hardcoded ID would silently assign the wrong profile rather than failing.
#
# Change Only What Is Wrong
#   Items already on the correct profile are never touched. That makes the tool safe to
#   re-run at any time and keeps the reported count meaningful — a non-zero result means
#   real drift, not just "it ran".
#
# Root Folder Is the Source of Truth
#   Classification comes from where the item actually lives, not from its metadata. The
#   classification scans own the harder question of whether an item is in the right root;
#   this tool just makes the profile agree with the answer already on disk.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
#   Idempotent     — only touches items whose current profile is wrong
#   API-only       — no file system changes; profile ID looked up by name at runtime
#   --dry-run mode — reports what would change without applying any updates
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   ARR_KIDS_PROFILE_NAME      — profile name for kids/anime (default: "Kids shows")
#   ARR_SONARR_DEFAULT_PROFILE — default Sonarr profile name (default: "Any")
#   ARR_RADARR_DEFAULT_PROFILE — default Radarr profile name (default: "Any (mine)")
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# arr_profile_enforcer.sh
#     Enforce profiles in both Sonarr and Radarr.
#
# arr_profile_enforcer.sh --dry-run
#     Show which items would be updated without making changes.
#
# arr_profile_enforcer.sh --sonarr-only
#     Run only Sonarr enforcement.
#
# arr_profile_enforcer.sh --radarr-only
#     Run only Radarr enforcement.
#
# arr_profile_enforcer.sh --log
#     Verbose output.
#
# ==============================================================================================

RUN_SONARR=true
RUN_RADARR=true

for arg in "$@"; do
    case "$arg" in
        --sonarr-only)  RUN_RADARR=false   ;;
        --radarr-only)  RUN_SONARR=false   ;;
    esac
done

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"
parse_args "$@"

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
# Consistent with the rest of Arrs_Stack/ and Tools/ — this issues bulk profile PUTs against
# the arrs and should not be runnable by an unprivileged account that happens to hold a key.
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root"
    exit 1
fi

# Bulk PUTs against the same items — two concurrent runs could interleave and leave a subset
# of series/movies on the wrong profile.
acquire_lock

detect_hosts

KIDS_PROFILE_NAME="${ARR_KIDS_PROFILE_NAME:-Kids shows}"
SONARR_DEFAULT="${ARR_SONARR_DEFAULT_PROFILE:-Any}"
RADARR_DEFAULT="${ARR_RADARR_DEFAULT_PROFILE:-Any (mine)}"

# ── Helpers ──────────────────────────────────────────────────────────────────
_arr_get() {
    local url="$1" key="$2" endpoint="$3"
    curl -sf --max-time 30 -H "X-Api-Key: $key" "$url/api/v3/$endpoint"
}

_arr_put() {
    local url="$1" key="$2" endpoint="$3" body="$4"
    curl -sf --max-time 60 -X PUT \
        -H "X-Api-Key: $key" \
        -H "Content-Type: application/json" \
        -d "$body" \
        "$url/api/v3/$endpoint"
}

_profile_id_by_name() {
    local profiles_json="$1" name="$2"
    php -r '
$profiles = json_decode(file_get_contents("php://stdin"), true);
$name = $argv[1];
foreach ($profiles as $p) {
    if (strcasecmp($p["name"], $name) === 0) { echo $p["id"]; exit; }
}
exit(1);
' "$name" <<< "$profiles_json"
}

_is_kids_path() {
    local path="$1"
    # Parameter expansion instead of external dirname+basename — called once per series/movie
    # (~4000 items combined), each call was forking two subprocesses. Measured 2026-07-17:
    # ~185x faster per equivalent call (0.39s vs 72.2s per 20K) for the identical result.
    local parent="${path%/*}"
    local dir="${parent##*/}"
    [[ "$dir" == *kids* || "$dir" == *anime* ]]
}

# ── Core enforcer ─────────────────────────────────────────────────────────────
# _enforce <label> <url> <api_key> <items_endpoint> <id_field> <editor_endpoint>
#           <kids_profile_id> <default_profile_id>
_enforce() {
    local label="$1" url="$2" key="$3" items_ep="$4"
    local id_field="$5" editor_ep="$6"
    local kids_id="$7" default_id="$8"

    local items
    items=$(_arr_get "$url" "$key" "$items_ep") || {
        error "[$label] Cannot reach $url"
        return 1
    }

    local to_kids=() to_default=()

    while IFS='|' read -r id profile_id path; do
        if _is_kids_path "$path"; then
            [[ "$profile_id" -ne "$kids_id" ]]    && to_kids+=("$id")
        else
            [[ "$profile_id" -ne "$default_id" ]] && to_default+=("$id")
        fi
    done < <(php -r '
$items = json_decode(file_get_contents("php://stdin"), true);
foreach ($items as $r) echo $r["id"]."|".$r["qualityProfileId"]."|".$r["path"]."\n";
' <<< "$items")

    local total=$(( ${#to_kids[@]} + ${#to_default[@]} ))
    log "arr_profile_enforcer" "[$label] ${#to_kids[@]} → $KIDS_PROFILE_NAME | ${#to_default[@]} → default | $total to fix"

    if [[ "$DRY_RUN" == true ]]; then
        echo "  [$label] DRY RUN — would set $KIDS_PROFILE_NAME on ${#to_kids[@]}, default on ${#to_default[@]}"
        return 0
    fi

    _bulk_update() {
        local profile_id="$1" prof_label="$2"
        shift 2
        local ids=("$@")
        [[ ${#ids[@]} -eq 0 ]] && return 0

        local ids_json
        ids_json=$(printf '%s,' "${ids[@]}")
        ids_json="[${ids_json%,}]"

        local result
        result=$(_arr_put "$url" "$key" "$editor_ep" \
            "{\"${id_field}\":${ids_json},\"qualityProfileId\":${profile_id}}") || {
            error "[$label] Bulk update failed for $prof_label"
            return 1
        }

        local updated
        updated=$(php -r 'echo count(json_decode(file_get_contents("php://stdin"), true));' <<< "$result")
        log "arr_profile_enforcer" "[$label] Set $prof_label on $updated items"
    }

    _bulk_update "$kids_id"    "$KIDS_PROFILE_NAME" "${to_kids[@]+"${to_kids[@]}"}"
    _bulk_update "$default_id" "default"             "${to_default[@]+"${to_default[@]}"}"
}

# ── Sonarr ────────────────────────────────────────────────────────────────────
if [[ "$RUN_SONARR" == true ]]; then
    require_var SONARR_URL
    require_var SONARR_API_KEY

    SONARR_PROFILES=$(_arr_get "$SONARR_URL" "$SONARR_API_KEY" "qualityprofile") || {
        error "Cannot reach Sonarr at $SONARR_URL"
        exit 1
    }

    SONARR_KIDS_ID=$(_profile_id_by_name "$SONARR_PROFILES" "$KIDS_PROFILE_NAME") || {
        error "Sonarr profile not found: '$KIDS_PROFILE_NAME'"
        exit 1
    }
    SONARR_DEFAULT_ID=$(_profile_id_by_name "$SONARR_PROFILES" "$SONARR_DEFAULT") || {
        error "Sonarr profile not found: '$SONARR_DEFAULT'"
        exit 1
    }

    _enforce "Sonarr" "$SONARR_URL" "$SONARR_API_KEY" \
        "series" "seriesIds" "series/editor" \
        "$SONARR_KIDS_ID" "$SONARR_DEFAULT_ID"
fi

# ── Radarr ────────────────────────────────────────────────────────────────────
if [[ "$RUN_RADARR" == true ]]; then
    require_var RADARR_URL
    require_var RADARR_API_KEY

    RADARR_PROFILES=$(_arr_get "$RADARR_URL" "$RADARR_API_KEY" "qualityprofile") || {
        error "Cannot reach Radarr at $RADARR_URL"
        exit 1
    }

    RADARR_KIDS_ID=$(_profile_id_by_name "$RADARR_PROFILES" "$KIDS_PROFILE_NAME") || {
        error "Radarr profile not found: '$KIDS_PROFILE_NAME'"
        exit 1
    }
    RADARR_DEFAULT_ID=$(_profile_id_by_name "$RADARR_PROFILES" "$RADARR_DEFAULT") || {
        error "Radarr profile not found: '$RADARR_DEFAULT'"
        exit 1
    }

    _enforce "Radarr" "$RADARR_URL" "$RADARR_API_KEY" \
        "movie" "movieIds" "movie/editor" \
        "$RADARR_KIDS_ID" "$RADARR_DEFAULT_ID"
fi

log "arr_profile_enforcer" "Done"
