#!/bin/bash
# ==============================================================================================
# ============================= Bulk Permissions Repair ========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Applies correct ownership and permissions to one or more specific paths.
# Targeted repair — faster than media_shares_permissions.sh, which processes
# every configured share. Use after failed transfers that left root:root ownership,
# containers writing as root before PUID/PGID was fixed, manual file copies, or
# new shares that need permissions applied before the next nightly run.
#
# Counts files with wrong ownership before fixing. A high count on a recently
# written share means a container has wrong PUID/PGID — add PUID=99 PGID=100
# to its Docker template. Common culprits: SABnzbd, qBittorrent, slskd.
#
# Side effect worth knowing: this applies owner/mode unconditionally, and chown/chmod
# restamp an inode's ctime even when the value doesn't change. sonarr/radarr/lidarr_cleanup.sh
# gate orphan deletion on ctime, so running this over a whole media root resets that clock
# and pauses orphan collection there for *_ORPHAN_AGE days. That is why the nightly
# media_shares_permissions.sh applies its passes conditionally. Harmless for the targeted
# repairs this tool is meant for — worth remembering before pointing it at an entire share.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# For each path given on the command line:
#
#   1. Path guard — refuse anything shallower than two components
#   2. Existence check — a missing path is a failure for that entry, not the run
#   3. Report scale — file count, dir count, total size, so the operator sees the job size
#   4. Count entries with wrong ownership (the diagnostic number in the summary)
#   5. chown -R PERMISSIONS_OWNER across the path
#   6. find -type d → chmod PERMISSIONS_DIR_MODE
#   7. find -type f → chmod PERMISSIONS_FILE_MODE
#
# Unlike the nightly media_shares_permissions.sh, steps 5–7 are unconditional — see the
# ctime note in PURPOSE above for why that distinction matters.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Unconditional by Design, Unlike the Nightly Job
#   media_shares_permissions.sh applies its passes conditionally to protect ctime as an
#   age signal for arr orphan collection. This tool deliberately does not: it exists to
#   repair paths that are known-wrong, where correctness matters more than preserving a
#   clock. That is exactly why it is a targeted manual tool and not a scheduled one.
#
# Permissions Model
#   Directories (PERMISSIONS_DIR_MODE, default 755):
#     Owner (nobody) — rwx  enter, list, create files
#     Group (users)  — r-x  enter and list
#     Others         — r-x  Samba guests can browse
#   Files (PERMISSIONS_FILE_MODE, default 664):
#     Owner (nobody) — rw   read + write
#     Group (users)  — rw   arrs can import and rename
#     Others         — r    Samba guests can read
#     No execute bit — media files are never executable
#
# Separate Passes
#   Directories and files are chmod'd in separate find passes. A combined pass
#   with mode 664 would wrongly strip the execute bit from directories, making
#   them untraversable.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   chown requires root — exits immediately if not running as root.
#
# Path Existence Check
#   Each path is verified before processing — missing paths log an error and
#   are skipped rather than silently passing.
#
# Silent on Success
#   Only failures and the wrong-owner diagnostic produce visible output.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   PERMISSIONS_OWNER
#       Owner applied to all paths. (default: nobody:users)
#
#   PERMISSIONS_DIR_MODE
#       chmod mode for directories. (default: 755)
#
#   PERMISSIONS_FILE_MODE
#       chmod mode for files. (default: 664)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# bulk_permissions_repair.sh /path/to/share [/another/path ...]
#     Apply ownership and permissions to each specified path.
#
# bulk_permissions_repair.sh /path/to/share --dry-run
#     Show wrong-owner count per path. No chown or chmod applied.
#
# bulk_permissions_repair.sh /path/to/share --log
#     Verbose output including per-path file counts and modes applied.
#
# ==============================================================================================

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 — chown requires root"
    exit 1
fi

acquire_lock


# detect_hosts() sets MY_ID — used in summary
detect_hosts

if [[ ${#PARSED_ARGS[@]} -eq 0 ]]; then
    error "No paths specified"
    error "Usage: bulk_permissions_repair.sh /path/to/share [/another/path] [--dry-run]"
    exit 1
fi

log "Dir mode:  ${PERMISSIONS_DIR_MODE:-755}"
log "File mode: ${PERMISSIONS_FILE_MODE:-664}"
log "Owner:     $PERMISSIONS_OWNER"
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no permissions will be changed"

# ==============================================================================================
# ━━━ Apply Permissions ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_PERMS Permissions Repair — $MY_ID ━━━"

START=$(date +%s)
PASS=()
FAIL=()
TOTAL_WRONG_OWNER=0

for share_path in "${PARSED_ARGS[@]}"; do
    [[ -z "$share_path" ]] && continue

    echo ""
    echo "━━━ $ICON_PERMS $(basename "$share_path") ━━━"

    # chown -R below. Paths come straight from the command line, so a spacing typo
    # ("/mnt/user /Movies" instead of "/mnt/user/Movies") would hand this a bare top-level
    # directory — and chown -R nobody:users on / or /etc breaks the system outright.
    # Require at least two path components; that still allows a deliberate whole-share
    # repair like /mnt/user while refusing /, /mnt, /etc, /boot and friends.
    _bpr_slashes="${share_path//[^\/]/}"
    if [[ "$share_path" != /* || "${#_bpr_slashes}" -lt 2 ]]; then
        error "$share_path — refusing: expected an absolute path at least 2 levels deep"
        FAIL+=("$share_path")
        continue
    fi

    if [[ ! -d "$share_path" ]]; then
        error "$share_path — not found"
        FAIL+=("$share_path")
        continue
    fi

    # Count files for context — warn level so user knows what they're in for on large shares
    FILE_COUNT=$(find "$share_path" -type f 2>/dev/null | wc -l)
    DIR_COUNT=$(find  "$share_path" -type d 2>/dev/null | wc -l)
    SIZE=$(du -sh "$share_path" 2>/dev/null | cut -f1)
    warn "$share_path — $FILE_COUNT files, $DIR_COUNT dirs ($SIZE)"

    # Count files with wrong ownership before fixing — diagnostic
    WRONG_OWNER=$(find "$share_path" \( ! -user nobody -o ! -group users \) \
        2>/dev/null | wc -l)
    if [[ "$WRONG_OWNER" -gt 0 ]]; then
        warn "$WRONG_OWNER file(s) with wrong ownership — fixing..."
        if [[ "$WRONG_OWNER" -gt 500 ]]; then
            warn "High wrong-owner count — check container PUID/PGID settings (should be PUID=99 PGID=100)"
            warn "Common culprits: SABnzbd, qBittorrent, slskd"
        fi
        TOTAL_WRONG_OWNER=$(( TOTAL_WRONG_OWNER + WRONG_OWNER ))
    else
        log "Ownership already correct — applying mode only"
    fi

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would apply:"
        warn "  chown -R $PERMISSIONS_OWNER $share_path"
        warn "  find -type d → chmod ${PERMISSIONS_DIR_MODE:-755}"
        warn "  find -type f → chmod ${PERMISSIONS_FILE_MODE:-664}"
        PASS+=("$(basename "$share_path")")
        continue
    fi

    CHOWN_OK=true
    CHMOD_DIR_OK=true
    CHMOD_FILE_OK=true

    # Apply ownership first
    log "Applying ownership: $PERMISSIONS_OWNER..."
    chown -R "$PERMISSIONS_OWNER" "$share_path" 2>/dev/null || CHOWN_OK=false

    # Apply directory permissions — separate pass (dirs need execute bit)
    log "Applying directory permissions: ${PERMISSIONS_DIR_MODE:-755}..."
    find "$share_path" -type d \
        -exec chmod "${PERMISSIONS_DIR_MODE:-755}" {} + 2>/dev/null || CHMOD_DIR_OK=false

    # Apply file permissions — no execute bit on media files
    log "Applying file permissions: ${PERMISSIONS_FILE_MODE:-664}..."
    find "$share_path" -type f \
        -exec chmod "${PERMISSIONS_FILE_MODE:-664}" {} + 2>/dev/null || CHMOD_FILE_OK=false

    if [[ "$CHOWN_OK" == true && "$CHMOD_DIR_OK" == true && "$CHMOD_FILE_OK" == true ]]; then
        echo "$ICON_UNLOCKED $(basename "$share_path") — permissions applied ✅"
        PASS+=("$(basename "$share_path")")
    else
        error "$(basename "$share_path") — repair failed"
        error "  chown: $CHOWN_OK  chmod dirs: $CHMOD_DIR_OK  chmod files: $CHMOD_FILE_OK"
        FAIL+=("$(basename "$share_path")")
    fi
done

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY PERMISSIONS REPAIR SUMMARY ━━━━━"
echo "$ICON_HOST    Identity:  $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_PERMS   Dir mode:  ${PERMISSIONS_DIR_MODE:-755}"
echo "$ICON_PERMS   File mode: ${PERMISSIONS_FILE_MODE:-664}"
echo "$ICON_PERMS   Owner:     $PERMISSIONS_OWNER"
echo "$ICON_TIME    Duration:  $(format_duration $(( END - START )))"
echo ""
[[ ${#PASS[@]} -gt 0 ]] && log "Pass: ${PASS[*]}"
[[ ${#FAIL[@]} -gt 0 ]] && echo "$ICON_ERROR Fail: ${FAIL[*]}"

if [[ "$TOTAL_WRONG_OWNER" -gt 0 ]]; then
    warn "Total wrong-owner files fixed: $TOTAL_WRONG_OWNER"
fi
echo ""

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no changes made"
elif [[ ${#FAIL[@]} -gt 0 ]]; then
    echo "$ICON_ERROR Status: SOME REPAIRS FAILED"
    notify "Permissions repair failed on $(hostname) — ${FAIL[*]}" \
        "Permissions Repair" "warning"
else
    echo "$ICON_DONE Status: done — ${#PASS[@]} path(s) repaired"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

[[ ${#FAIL[@]} -gt 0 ]] && exit 1
exit 0