#!/bin/bash
# ==============================================================================================
# ======================= Lidarr Duplicate Artist Cleanup =======================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Detects and resolves duplicate artist entries in Lidarr's library — cases where the
# same display name (case-insensitive) is backed by two different MusicBrainz artist
# IDs. This happens when a search or list sync matches a same-named-but-different real
# artist and adds it alongside the one already in the library. From that point on,
# every completed download for that display name throws MultipleArtistsFoundException
# and can never import — Lidarr correctly refuses to guess which of the two it means.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# For each case-insensitive duplicate artist name found:
#
#   Only one side has any tracked files
#     → The zero-file side is a phantom — delete it (deleteFiles=false, nothing on disk
#       to lose) and add its MusicBrainz ID to Lidarr's Import List Exclusions so it
#       can't be silently re-added by a future list sync. Then trigger a refresh on the
#       surviving artist so anything that was stuck on this exact ambiguity resolves
#       immediately instead of waiting for Lidarr's own next check cycle.
#
#   Both sides have files, but their album titles don't overlap at all
#     → Genuinely two different real artists sharing a name (e.g. a band's classic
#       lineup vs. a later solo era with the same stage name). Not a bug — left alone.
#
#   Both sides have files AND overlapping album titles
#     → The one case actually risky to automate: could mean real content is split
#       across two entries and needs an actual merge, not a delete. Untouched,
#       notified for manual review.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Never Delete Real Content
#   Only the zero-tracked-file side of a pair is ever deleted. Anything with files is
#   either left alone (disjoint albums) or flagged for a human (overlapping albums) —
#   never auto-removed.
#
# Block Re-Addition At The Source
#   A phantom that keeps coming back is worse than one that was never cleaned —
#   Import List Exclusion is Lidarr's own mechanism for "never auto-add this again."
#
# Silent When Clean
#   No duplicates found, or all duplicates are the simple phantom case — minimal
#   output. Only ambiguous (overlapping-album) pairs produce a notification.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Required by the container interaction and state writes.
#
# Lock Acquisition
#   acquire_lock prevents overlapping runs racing on the same artist IDs.
#
# Host Detection
#   detect_hosts() aliases LIDARR_URL / LIDARR_API_KEY.
#
# jq Dependency Check
#   Fails fast if jq is missing — duplicate detection and every file-count read
#   depend on it, and an absent jq would evaluate counts to empty and make every
#   artist look like a zero-file phantom.
#
# API Reachability + Version Gate
#   check_api then check_arr_version against LIDARR_VERSION_MAJOR before any read.
#
# Empty Library Abort
#   A response of 0 artists aborts. An empty list is indistinguishable from
#   "no duplicates" and must never be read as a clean result.
#
# Tracked-Count Floor
#   check_tracked_count_floor against the baseline shared with lidarr_cleanup.sh.
#   A library-wide desync — mid full-rescan, for example — makes trackFileCount read
#   far below reality for many artists at once. Confirmed 2026-07-16: without this,
#   both sides of a genuinely-real duplicate (ROMES) read as 0-file phantoms and the
#   wrong one would have been deleted. Deliberately reuses lidarr_cleanup.sh's own
#   baseline so every script depending on tracked counts shares one answer to "is
#   Lidarr's data trustworthy right now" rather than forming a separate opinion.
#
# Files Never Deleted
#   Removal passes deleteFiles=false. Only the phantom Lidarr entry is dropped;
#   nothing on disk is touched, so a wrong call costs a re-add, not media.
#
# Phantom-Only Deletion
#   Only the zero-file side of a duplicate pair is ever removed. If both sides hold
#   files, or neither does unambiguously, the pair is flagged for manual review
#   instead — the script never picks a winner between two real artists.
#
# Manual Review Reporting
#   Flagged pairs are named in the summary and notification so an ambiguous
#   duplicate surfaces as a decision to make rather than disappearing silently.
#
# Dry Run Support
#   --dry-run reports every deletion and exclusion and performs none.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# host*.conf
#
#   HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY — aliased by detect_hosts()
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   lidarr_duplicate_artist_cleanup.sh              — normal run
#   lidarr_duplicate_artist_cleanup.sh --dry-run    — preview, no deletions or exclusions
#   lidarr_duplicate_artist_cleanup.sh --log        — verbose per-pair output
#   lidarr_duplicate_artist_cleanup.sh --status     — show config and exit
#
# ==============================================================================================

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"
    exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
    error "jq not found — required for JSON parsing"
    notify "lidarr_duplicate_artist_cleanup failed on $(hostname) — jq not installed" \
        "Lidarr Duplicate Cleanup" "warning"
    exit 1
fi

acquire_lock "wait"

# detect_hosts() sets MY_ID and aliases LIDARR_URL, LIDARR_API_KEY
detect_hosts

if [[ -z "${LIDARR_URL:-}" ]] || [[ -z "${LIDARR_API_KEY:-}" ]]; then
    info "Lidarr not configured on $MY_ID ($LOCAL_SERVER_NAME) — skipping"
    exit 0
fi

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

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
    echo "$ICON_HOST    Identity:    $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_GEAR    Lidarr URL:  $LIDARR_URL"
    echo "$ICON_GEAR    Lidarr ver:  v${LIDARR_VERSION_MAJOR:-3} expected"
    echo "$ICON_GEAR    Dry Run:     $DRY_RUN"
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

# ==============================================================================================
# ━━━ Safety Checks ━━━
# ==============================================================================================
check_api "$LIDARR_URL" "Lidarr" 10 || {
    notify "Lidarr duplicate cleanup aborted on $(hostname) — API unreachable" \
        "Lidarr Duplicate Cleanup" "warning"
    exit 1
}
check_arr_version "$LIDARR_URL" "$LIDARR_API_KEY" "v1" "${LIDARR_VERSION_MAJOR:-3}" "Lidarr" || exit 1

TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT

# ==============================================================================================
# ━━━ Fetch Artists (cache-aware — waits out an active rescan rather than trusting a
# mid-scan number, falls back to cache if Lidarr's still busy after the strike limit) ━━━
# ==============================================================================================
ARTISTS=$(arr_get_tracked_data "lidarr" "$LIDARR_URL" "$LIDARR_API_KEY" "v1")
if [[ -z "$ARTISTS" ]]; then
    warn "Lidarr busy and no usable cache — deferring to next scheduled run"
    exit 0
fi

ARTIST_COUNT=$(echo "$ARTISTS" | jq 'length' 2>/dev/null)
if [[ -z "$ARTIST_COUNT" || "$ARTIST_COUNT" -eq 0 ]]; then
    error "API returned 0 artists — aborting to avoid acting on empty data"
    notify "Lidarr duplicate cleanup aborted on $(hostname) — 0 artists returned" \
        "Lidarr Duplicate Cleanup" "warning"
    exit 1
fi
log "$ICON_GEAR Fetched $ARTIST_COUNT artists"

# Safety Layer — same shared baseline as lidarr_cleanup.sh. A library-wide desync (e.g. mid
# full-rescan) can make trackFileCount read far lower than reality for many artists at once —
# confirmed 2026-07-16, where this exact scenario would have made the script see both sides
# of a genuinely-real duplicate (ROMES) as 0-file phantoms and delete the wrong thing entirely.
# Reuses lidarr_cleanup.sh's own baseline file — one shared "is Lidarr's data trustworthy
# right now" answer for every script that depends on tracked counts, not a separate opinion
# per script.
TOTAL_TRACKED=$(echo "$ARTISTS" | jq '[.[].statistics.trackFileCount] | add' 2>/dev/null)
check_tracked_count_floor "${TOTAL_TRACKED:-0}" "$LIDARR_TRACKED_COUNT_FILE" "${LIDARR_MIN_TRACKED_PCT:-50}" "Lidarr Duplicate Cleanup"

# ==============================================================================================
# ━━━ Find Case-Insensitive Duplicate Names ━━━
# ==============================================================================================
DUP_NAMES=$(echo "$ARTISTS" | jq -r '.[].artistName' | tr '[:upper:]' '[:lower:]' | sort | uniq -d)

if [[ -z "$DUP_NAMES" ]]; then
    echo "Lidarr — clean ✅ no duplicate artist names"
    exit 0
fi

DUP_COUNT=$(echo "$DUP_NAMES" | grep -c .)
warn "Found $DUP_COUNT duplicate artist name(s)"

DELETED=0
EXCLUDED=0
LEFT_ALONE=0
FLAGGED=0
FLAGGED_NAMES=()

# ==============================================================================================
# ━━━ Resolve Each Duplicate ━━━
# ==============================================================================================
while IFS= read -r lname; do
    [[ -z "$lname" ]] && continue

    MEMBERS=$(echo "$ARTISTS" | jq -c --arg n "$lname" '[.[] | select((.artistName|ascii_downcase)==$n)]')

    NONZERO_IDS=()
    ZERO_MEMBERS_FILE="$TMP_DIR/zero_${RANDOM}.jsonl"
    : > "$ZERO_MEMBERS_FILE"

    while IFS= read -r member; do
        [[ -z "$member" ]] && continue
        fc=$(echo "$member" | jq -r '.statistics.trackFileCount // 0')
        if [[ "$fc" -gt 0 ]]; then
            NONZERO_IDS+=("$(echo "$member" | jq -r '.id')")
        else
            echo "$member" >> "$ZERO_MEMBERS_FILE"
        fi
    done < <(echo "$MEMBERS" | jq -c '.[]')

    if [[ "${#NONZERO_IDS[@]}" -le 1 ]]; then
        # Simple phantom case — delete every zero-file member, keep the real one (if any)
        while IFS= read -r zmember; do
            [[ -z "$zmember" ]] && continue
            zid=$(echo "$zmember" | jq -r '.id')
            zname=$(echo "$zmember" | jq -r '.artistName')
            zmbid=$(echo "$zmember" | jq -r '.foreignArtistId')

            if [[ "$DRY_RUN" == true ]]; then
                warn "  DRY RUN — would delete phantom: $zname ($zid) and exclude MBID $zmbid"
                continue
            fi

            if curl -sf --max-time 15 -X DELETE \
                "${LIDARR_URL}/api/v1/artist/${zid}?deleteFiles=false" \
                -H "X-Api-Key: $LIDARR_API_KEY" >/dev/null 2>&1; then
                (( DELETED++ ))
                log "  $ICON_TRASH Deleted phantom: $zname ($zid)"
            else
                warn "  Failed to delete phantom: $zname ($zid)"
                continue
            fi

            EXCL_PAYLOAD=$(jq -c -n --arg fid "$zmbid" --arg name "$zname" \
                '{foreignId:$fid, artistName:$name}')
            if curl -sf --max-time 15 -X POST \
                "${LIDARR_URL}/api/v1/importlistexclusion" \
                -H "X-Api-Key: $LIDARR_API_KEY" -H "Content-Type: application/json" \
                -d "$EXCL_PAYLOAD" >/dev/null 2>&1; then
                (( EXCLUDED++ ))
                log "    Added to import list exclusions: $zmbid"
            else
                warn "    Could not add exclusion for $zname ($zmbid) — may already exist"
            fi
        done < "$ZERO_MEMBERS_FILE"

        # Nudge the surviving real artist so anything stuck on this ambiguity
        # resolves now rather than waiting for Lidarr's own next check cycle.
        if [[ "${#NONZERO_IDS[@]}" -eq 1 && "$DRY_RUN" != true ]]; then
            curl -sf --max-time 15 -X POST \
                "${LIDARR_URL}/api/v1/command" \
                -H "X-Api-Key: $LIDARR_API_KEY" -H "Content-Type: application/json" \
                -d "{\"name\":\"RefreshArtist\",\"artistId\":${NONZERO_IDS[0]}}" >/dev/null 2>&1
        fi
    else
        # 2+ members have real content — same name, need to know if it's the same
        # artist actually split (album overlap) or genuinely different acts (no overlap).
        #
        # Titles must be deduped WITHIN each artist before checking overlap ACROSS artists —
        # a single artist can legitimately list the same album title twice (a reissue, a
        # deluxe edition under an unchanged title). Confirmed 2026-07-16: Alice Cooper's own
        # catalog has "Lace and Whiskey" and "School's Out" each listed twice under one
        # artist ID — treating that as "overlap" false-flagged a genuinely disjoint pair
        # (band-era vs. solo-era) as ambiguous when it wasn't.
        OVERLAP=false
        declare -A GLOBAL_TITLES
        for nid in "${NONZERO_IDS[@]}"; do
            declare -A THIS_ARTIST_TITLES
            while IFS= read -r title; do
                [[ -z "$title" ]] && continue
                THIS_ARTIST_TITLES["$title"]=1
            done < <(curl -sf --max-time 15 "${LIDARR_URL}/api/v1/album?artistId=${nid}" \
                -H "X-Api-Key: $LIDARR_API_KEY" 2>/dev/null | jq -r '.[].title')

            for title in "${!THIS_ARTIST_TITLES[@]}"; do
                [[ -n "${GLOBAL_TITLES[$title]:-}" ]] && OVERLAP=true
                GLOBAL_TITLES["$title"]=1
            done
            unset THIS_ARTIST_TITLES
        done
        unset GLOBAL_TITLES

        if [[ "$OVERLAP" == true ]]; then
            warn "  $ICON_WARN Ambiguous duplicate needs manual review: $lname (both have files, albums overlap)"
            FLAGGED_NAMES+=("$lname")
            (( FLAGGED++ ))
        else
            log "  $lname — different real artists sharing a name, no album overlap, no action"
            (( LEFT_ALONE++ ))
        fi
    fi
done <<< "$DUP_NAMES"

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY LIDARR DUPLICATE ARTIST CLEANUP SUMMARY ━━━━━"
echo "$ICON_HOST    Identity:  $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TRASH   Deleted:   $DELETED phantom artist(s)"
echo "$ICON_GEAR    Excluded:  $EXCLUDED (blocked from future re-add)"
echo "$ICON_SKIP    Left alone: $LEFT_ALONE (different real artists, no overlap)"
echo "$ICON_WARN    Flagged:   $FLAGGED (needs manual review)"
for n in "${FLAGGED_NAMES[@]}"; do
    echo "    - $n"
done
echo ""

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no changes made"
elif [[ "$FLAGGED" -gt 0 ]]; then
    notify "Lidarr duplicate cleanup on $(hostname) — $DELETED phantom(s) removed, $FLAGGED artist(s) need manual review: ${FLAGGED_NAMES[*]}" \
        "Lidarr Duplicate Cleanup" "warning"
elif [[ "$DELETED" -gt 0 ]]; then
    echo "$ICON_DONE Status: cleaned $DELETED phantom artist(s), nothing needs review"
else
    echo "$ICON_DONE Status: no action needed"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

exit 0
