#!/bin/bash
# ==============================================================================================
# ================================= Media Cleaner ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Remove scene debris, tool artifacts, and unsafe files from media shares
# before the orphan scan — so arr cleanup only encounters actual media files.
# Runs second in the daily window, after permissions and before arr cleanup.
# Two profiles — anime and media — each with their own folder list and patterns.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Profiles:
#   anime — cleans ANIME_CLEAN_FOLDERS using ANIME_FILE_PATTERNS
#   media — cleans MEDIA_CLEAN_FOLDERS using MEDIA_FILE_PATTERNS (adds *.iso *.lrc)
#
# Scene debris — left by scene releases and download clients:
#   *.sfv *.md5 *.sha1        — checksums — useless post-download
#   *.nzb *.torrent           — download files left by clients
#   *.url *.lnk *.info *.diz  — scene metadata
#   *.sample* *.proof*        — scene samples — never needed in library
#   *sync-conflict*           — Syncthing conflict copies
#   *.rar *.zip *.7z *.ace    — archives — source not needed after extraction
#   *.r00-*.r09 *.srr         — multi-part rar segments and repair files
#   *.001 *.002 *.003         — split archive parts
#   *.gz *.tar *.bz2          — linux archives
#
# Tool artifacts — incomplete or stale files from download clients:
#   *.!ut *.!qB               — uTorrent / qBittorrent incomplete markers
#   *.crdownload *.opdownload — Chrome / Opera incomplete downloads
#   *.part                    — partial download files
#
# Unsafe files — executables that should never appear in a media folder:
#   *.exe *.scr *.com         — Windows executables
#   *.bat *.cmd *.vbs *.ps1   — Windows scripts
#   *.msi *.dll *.sys         — Windows system files
#   *.sh                      — shell scripts in media folders = suspicious
#
# Media profile also removes: *.iso *.lrc
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Pre-Scan Cleanup
#   Runs before arr cleanup scripts so orphan detection only encounters actual
#   media files. Scene debris and download artifacts would otherwise appear as
#   untracked files and inflate false-positive orphan counts.
#
# Profile Separation
#   Anime and media share different cleanup patterns because their content
#   differs. *.lrc (lyrics) and *.iso belong in media cleanup but not anime.
#   Separate profiles prevent cross-contamination of rules.
#
# Conservative by Default
#   Only explicitly listed patterns are removed. The script never guesses
#   at file intent — if a pattern is not in the list, the file is untouched.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Media files are owned by container users; deleting them requires root.
#
# Profile Required
#   Exits with usage if no profile is given. There is no default profile — an
#   unspecified profile must never fall through to cleaning something.
#
# Lock Acquisition
#   acquire_lock "wait" — waits for a previous run to finish rather than
#   skipping, so a long anime pass does not cause the media pass to be dropped.
#
# Host Detection
#   detect_hosts() aliases HOST*_ANIME_CLEAN_FOLDERS / HOST*_MEDIA_CLEAN_FOLDERS
#   to the correct host's values.
#
# Empty Array Guards
#   Exits cleanly if the resolved folder list or pattern list is empty. An empty
#   pattern list would otherwise build a find with no -iname terms and match
#   every file in the tree.
#
# Clean Path Depth Guard
#   Every folder must be an absolute path at least three levels deep before it is
#   scanned. The patterns include *.sh, *.zip, *.rar and *.exe, so a truncated
#   entry like /mnt/user — which passes an existence check — would delete
#   matching files across every share on the array.
#
# Folder Existence
#   Missing folders are skipped with a warning; remaining folders still process.
#
# Explicit Pattern List
#   Only patterns named in ANIME_FILE_PATTERNS / MEDIA_FILE_PATTERNS are removed.
#   The script never infers intent from file size, age, or location.
#
# Count Before Delete
#   Matching files are counted first; a folder with zero matches short-circuits
#   before any rm is constructed.
#
# Dry Run Support
#   --dry-run lists every file that would be deleted and removes nothing.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# host*.conf
#
#   HOST*_ANIME_CLEAN_FOLDERS — folders cleaned by the anime profile on this host
#   HOST*_MEDIA_CLEAN_FOLDERS — folders cleaned by the media profile on this host
#   Aliased by detect_hosts() — script uses ANIME_CLEAN_FOLDERS / MEDIA_CLEAN_FOLDERS
#
# master.conf
#
#   ANIME_FILE_PATTERNS  — file patterns removed by the anime profile
#   MEDIA_FILE_PATTERNS  — file patterns removed by the media profile
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   media_cleaner.sh anime              — clean anime shares
#   media_cleaner.sh media              — clean media shares
#   media_cleaner.sh anime --dry-run    — preview anime clean, no deletions
#   media_cleaner.sh media --dry-run    — preview media clean, no deletions
#   media_cleaner.sh anime --log        — verbose output
#   media_cleaner.sh anime --status     — show config and exit
#
# ==============================================================================================

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

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

# ── Separate profile argument from flags ──────────────────────────────────────────────────────
# Profile (anime|media) is a positional arg — separate before parse_args sees flags
PROFILE=""
RAW_ARGS=()

for ARG in "$@"; do
    case "$ARG" in
        anime|media) PROFILE="$ARG" ;;
        *)           RAW_ARGS+=("$ARG") ;;
    esac
done

parse_args "${RAW_ARGS[@]}"

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

if [[ -z "$PROFILE" ]]; then
    error "No profile specified"
    error "Usage: media_cleaner.sh <anime|media> [--dry-run] [--log] [--status]"
    exit 1
fi

acquire_lock "wait"

# detect_hosts() sets MY_ID and aliases HOST*_ANIME/MEDIA_CLEAN_FOLDERS
detect_hosts


# Resolve profile folders and patterns
case "$PROFILE" in
    anime)
        CLEAN_FOLDERS=("${ANIME_CLEAN_FOLDERS[@]}")
        FILE_PATTERNS=("${ANIME_FILE_PATTERNS[@]}")
        ;;
    media)
        CLEAN_FOLDERS=("${MEDIA_CLEAN_FOLDERS[@]}")
        FILE_PATTERNS=("${MEDIA_FILE_PATTERNS[@]}")
        ;;
    *)
        error "Unknown profile: $PROFILE — must be anime or media"
        exit 1
        ;;
esac

# Empty array guards
if [[ ${#CLEAN_FOLDERS[@]} -eq 0 ]]; then
    warn "No folders configured for profile '$PROFILE' on $MY_ID"
    warn "Check HOST*_${PROFILE^^}_CLEAN_FOLDERS in host*.conf"
    exit 0
fi

if [[ ${#FILE_PATTERNS[@]} -eq 0 ]]; then
    warn "No file patterns configured for profile '$PROFILE'"
    warn "Check ${PROFILE^^}_FILE_PATTERNS in master.conf"
    exit 0
fi

log "Profile:  $PROFILE"
log "Folders:  ${#CLEAN_FOLDERS[@]}"
log "Patterns: ${#FILE_PATTERNS[@]}"

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
    echo "$ICON_HOST    Identity: $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_GEAR    Profile:  $PROFILE"
    echo "$ICON_CLEAN   Folders:"
    for f in "${CLEAN_FOLDERS[@]}"; do
        echo "              $f"
    done
    echo "$ICON_TRASH   Patterns: ${FILE_PATTERNS[*]}"
    echo "$ICON_GEAR    Dry Run:  $DRY_RUN"
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be deleted"

# ==============================================================================================
# ━━━ Media Cleaner ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_CLEAN Media Cleaner — $PROFILE — $MY_ID ━━━"
echo ""

START=$(date +%s)
TOTAL_REMOVED=0
FAILED=()
SKIPPED=()

for FOLDER in "${CLEAN_FOLDERS[@]}"; do
    FOLDER_NAME=$(basename "$FOLDER")
    echo "━━━ $ICON_CLEAN $FOLDER_NAME ━━━"

    # The pattern list includes *.sh, *.zip, *.rar and *.exe. A truncated entry such as
    # /mnt/user passes the -d check below and would sweep every share on the array, so
    # require an absolute path at least three levels deep before scanning anything.
    _depth="${FOLDER//[^\/]/}"
    if [[ -z "$FOLDER" || "$FOLDER" != /* || "${#_depth}" -lt 3 ]]; then
        error "Refusing to clean unsafe path: '${FOLDER:-empty}' — expected an absolute path at least 3 levels deep"
        notify "Media cleaner ($PROFILE) refused unsafe path on $(hostname): '${FOLDER:-empty}'" \
            "Media Cleaner" "warning"
        FAILED+=("${FOLDER_NAME:-empty}")
        echo ""
        continue
    fi

    if [[ ! -d "$FOLDER" ]]; then
        warn "$FOLDER_NAME not found — skipping"
        SKIPPED+=("$FOLDER_NAME")
        echo ""
        continue
    fi

    # Build find command dynamically from FILE_PATTERNS array
    CMD=(find "$FOLDER" -type f \()
    for (( i = 0; i < ${#FILE_PATTERNS[@]}; i++ )); do
        CMD+=(-iname "${FILE_PATTERNS[i]}")
        if [[ $i -lt $(( ${#FILE_PATTERNS[@]} - 1 )) ]]; then
            CMD+=(-o)
        fi
    done
    CMD+=(\))

    # Count matching files before acting
    FILE_COUNT=$("${CMD[@]}" 2>/dev/null | wc -l)

    if [[ "$FILE_COUNT" -eq 0 ]]; then
        echo "$FOLDER_NAME — clean ✅"
        echo ""
        continue
    fi

    warn "$ICON_TRASH $FILE_COUNT file(s) to remove from $FOLDER_NAME"

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — files that would be deleted:"
        "${CMD[@]}" 2>/dev/null | while IFS= read -r f; do
            echo "    $ICON_TRASH $f"
        done
    else
        CLEAN_CMD=("${CMD[@]}" -exec rm -f {} +)
        if "${CLEAN_CMD[@]}" 2>/dev/null; then
            echo "$FOLDER_NAME — $FILE_COUNT file(s) removed"
            TOTAL_REMOVED=$(( TOTAL_REMOVED + FILE_COUNT ))
        else
            error "$FOLDER_NAME — cleanup failed"
            FAILED+=("$FOLDER_NAME")
        fi
    fi

    echo ""
done

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo "━━━━━ $ICON_SUMMARY MEDIA CLEANER SUMMARY ━━━━━"
echo "$ICON_HOST    Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR    Profile:  $PROFILE"
echo "$ICON_TIME    Duration: $(format_duration $(( END - START )))"
[[ ${#SKIPPED[@]} -gt 0 ]] && warn "Skipped:  ${SKIPPED[*]} (folders not found)"
[[ ${#FAILED[@]} -gt 0 ]]  && echo "$ICON_ERROR   Failed:   ${FAILED[*]}"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no files deleted"
elif [[ ${#FAILED[@]} -gt 0 ]]; then
    echo "$ICON_ERROR   Status: SOME FOLDERS FAILED"
    notify "Media cleaner ($PROFILE) failed on $(hostname) — ${FAILED[*]}" \
        "Media Cleaner" "warning"
elif [[ "$TOTAL_REMOVED" -eq 0 ]]; then
    echo "$ICON_DONE Status: clean — nothing to remove"
else
    warn "$ICON_TRASH Removed: $TOTAL_REMOVED file(s)"
    notify "Media cleaner ($PROFILE) on $(hostname) — $TOTAL_REMOVED file(s) removed" \
        "Media Cleaner" "warning"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

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