#!/bin/bash
# ==============================================================================================
# ================================= Lidarr Missing Art =========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Fetch missing album and artist artwork for the Lidarr music library. Downloads
# only what is absent — never overwrites existing files. Idempotent re-runs are
# safe.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Artwork targets per album folder:   cover.jpg  cdart.png  back.jpg
# Artwork targets per artist folder:  folder.jpg  fanart.jpg  clearlogo.png  banner.jpg
#
# Sources (tried in order, first success wins):
#   Album covers:  fanart.tv → iTunes fallback
#   Artist art:    fanart.tv → Deezer fallback → Last.fm fallback
#
# Reads from Lidarr API only — no writes back to Lidarr. Never modifies audio
# tags or renames media files. Only writes missing artwork files to existing
# album/artist directories.
#
# Cache-first, both layers (2026-07-17). The artist list comes from the shared tracked-data
# cache via arr_get_tracked_data() — fresh (kept warm every 30min by arr_cache_prefill.sh),
# live fetch as fallback. The per-artist track-file walk used to build the album→directory
# map now reads lidarr_cleanup.sh's write-through cache first (arr_get_cached_items() —
# lidarr_cleanup.sh runs earlier in the same nightly window and already does this exact
# walk for its own cleanup decisions), falling back to its own live per-artist walk only if
# that cache is missing or from outside the current window.
#
# Negative art cache (2026-07-28). fanart.tv has no cdart/back art for most of the long tail —
# roughly 80% of this library — so the nightly run was re-asking about the same ~11.8K albums
# every night and getting the same nothing back. Measured 2h41m for 3 images fetched. A flat TSV
# in DATA_DIR now remembers "upstream has no <art type> for <mbid>" and skips the API call
# entirely until LIDARR_ART_RECHECK_DAYS has passed, so new fanart.tv contributions are still
# picked up, just monthly instead of nightly. Only genuine no-art-upstream results are cached —
# a failed download of a URL that did exist stays retryable on the next run.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Additive Only
#   The script only adds missing files — it never overwrites existing artwork
#   or touches audio files. Re-running after a partial fetch completes exactly
#   where it left off with no side effects.
#
# Source Fallback Chain
#   Multiple sources are tried in order of quality preference. fanart.tv is
#   primary; fallbacks exist so partial coverage is better than none. A failed
#   primary never blocks the fallback from running.
#
# External API Courtesy
#   Rate limiting and parallel job caps prevent hammering fanart.tv and other
#   external APIs. Burst behaviour during large initial runs would risk
#   temporary blocks that break future scheduled fetches.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
#   acquire_lock           — prevents concurrent runs during large library scans
#   curl + jq check        — fail fast if tools missing
#   API reachability       — verified before processing begins
#   detect_hosts()         — exits cleanly if LIDARR_URL empty (HOST2, no Lidarr)
#   Skip existing          — never overwrites, idempotent re-runs are safe
#   Min file size check    — rejects corrupt/placeholder downloads (LIDARR_ART_MIN_SIZE)
#   Parallel job cap       — LIDARR_ART_MAX_PARALLEL — avoids hammering external APIs
#   Download retries       — LIDARR_ART_RETRIES attempts per image before giving up
#   Rate limiting          — LIDARR_ART_SLEEP_BETWEEN between fanart.tv API calls
#   Negative cache         — skips entities whose every missing target is a known upstream miss
#   Cache expiry on merge  — entries past the recheck window are dropped, file stays bounded
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# host*.conf
#
#   HOST1_LIDARR_URL / HOST1_LIDARR_API_KEY
#   Aliased by detect_hosts() — script uses LIDARR_URL / LIDARR_API_KEY
#
# master.conf
#
#   FANART_API_KEY           — fanart.tv API key
#   LASTFM_API_KEY           — last.fm API key
#   LIDARR_ART_MIN_SIZE      — minimum valid download size in bytes
#   LIDARR_ART_MAX_PARALLEL  — concurrent background download jobs
#   LIDARR_ART_RETRIES       — download retry attempts per image
#   LIDARR_ART_SLEEP_BETWEEN — seconds between fanart.tv API calls
#   LIDARR_ART_RECHECK_DAYS  — days before re-querying art upstream didn't have
#   LIDARR_ART_MISS_CACHE    — path to the negative cache TSV
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   lidarr_missing_art.sh              — fetch all missing artwork
#   lidarr_missing_art.sh --dry-run    — preview without downloading
#   lidarr_missing_art.sh --log        — verbose per-item output
#   lidarr_missing_art.sh --refresh    — ignore the negative cache, re-query everything
#   lidarr_missing_art.sh --status     — show config and exit
#
# ==============================================================================================

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

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

parse_args "$@"

# --refresh ignores the negative cache for this run — use after fanart.tv has had time to gain
# new contributions, or to re-prove a miss set by hand. Does not clear the cache; misses found
# this run simply overwrite their old stamps.
ART_REFRESH=false
for _arg in "${PARSED_ARGS[@]}"; do
    [[ "$_arg" == "--refresh" ]] && ART_REFRESH=true
done
unset _arg

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

if ! command -v curl >/dev/null 2>&1; then
    error "curl not found — required for API calls"
    exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
    error "jq not found — required for JSON parsing"
    exit 1
fi

acquire_lock

detect_hosts

# HOST guard — Lidarr runs on HOST1 only
if [[ -z "$LIDARR_URL" ]]; then
    echo "Lidarr not configured for $MY_ID — skipping"
    exit 0
fi

# Build path map from MY_ID's Lidarr path map
declare -A ARR_PATH_MAP
local_path_map_var="${MY_ID}_LIDARR_PATH_MAP"
eval "for _key in \"\${!${local_path_map_var}[@]}\"; do
    ARR_PATH_MAP[\"\$_key\"]=\"\${${local_path_map_var}[\$_key]}\"
done"
unset _key

info "$MY_ID ($LOCAL_SERVER_NAME) — tools OK"
log "$ICON_GEAR Config: url=${LIDARR_URL}"

# Defaulted here rather than relying solely on master.conf: Configurations/master.conf is
# gitignored, so these reach a node through conf_upgrade seeding them from
# Deployment/master.conf.template — which lands in the same window as this script, not before
# it. Same guard style as arr_corruption_scan.sh's state file.
LIDARR_ART_RECHECK_DAYS="${LIDARR_ART_RECHECK_DAYS:-30}"
LIDARR_ART_MISS_CACHE="${LIDARR_ART_MISS_CACHE:-$DATA_DIR/lidarr_art_miss_cache.tsv}"

# ==============================================================================================
# ━━━ 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_NET     Fanart key:  $([[ -n "${FANART_API_KEY:-}" ]] && echo "set" || echo "not set")"
    echo "$ICON_NET     LastFM key:  $([[ -n "${LASTFM_API_KEY:-}" ]] && echo "set" || echo "not set")"
    echo "$ICON_GEAR    Min size:    ${LIDARR_ART_MIN_SIZE} bytes"
    echo "$ICON_GEAR    Parallel:    $LIDARR_ART_MAX_PARALLEL jobs"
    echo "$ICON_RETRY   Retries:     $LIDARR_ART_RETRIES"
    echo "$ICON_TIME    API sleep:   ${LIDARR_ART_SLEEP_BETWEEN}s"
    echo "$ICON_GEAR    Miss cache:  $LIDARR_ART_MISS_CACHE ($([[ -f "$LIDARR_ART_MISS_CACHE" ]] && wc -l < "$LIDARR_ART_MISS_CACHE" || echo 0) entries)"
    echo "$ICON_TIME    Recheck:     every ${LIDARR_ART_RECHECK_DAYS}d"
    echo "$ICON_NOTIFY  Notify:      unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${MY_DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
    echo "$ICON_GEAR    Dry Run:     $DRY_RUN"
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

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

# ── Temp dir for subshell fetch/fail counters ─────────────────────────────────────────────────
LIDARR_TMP=$(mktemp -d)
trap '_release_all_locks; rm -rf "$LIDARR_TMP"' EXIT
touch "$LIDARR_TMP/album_fetches" "$LIDARR_TMP/album_nourl" "$LIDARR_TMP/album_dlfail" \
      "$LIDARR_TMP/artist_fetches" "$LIDARR_TMP/artist_nourl" "$LIDARR_TMP/artist_dlfail" \
      "$LIDARR_TMP/album_misses" "$LIDARR_TMP/artist_misses"

# ── Negative art cache state ──────────────────────────────────────────────────────────────────
NOW=$(date +%s)
ART_RECHECK_SECS=$(( LIDARR_ART_RECHECK_DAYS * 86400 ))
mkdir -p "$(dirname "$LIDARR_ART_MISS_CACHE")"
touch "$LIDARR_ART_MISS_CACHE"

declare -A ART_MISS
while IFS=$'\t' read -r _m_key _m_stamp; do
    [[ -n "$_m_key" ]] && ART_MISS["$_m_key"]="$_m_stamp"
done < "$LIDARR_ART_MISS_CACHE"
unset _m_key _m_stamp

# ==============================================================================================
# ── FUNCTIONS ─────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

curl_json() {
    curl -s --connect-timeout 5 --max-time 20 "$1"
}

job_count() {
    jobs -rp | wc -l
}

wait_for_slot() {
    while (( $(job_count) >= LIDARR_ART_MAX_PARALLEL )); do
        sleep 0.2
    done
}

# Downloads URL to dest only if dest doesn't exist and downloaded size >= MIN_SIZE.
# Returns 0 on success or skip (file already exists), 1 when the source had no URL to offer,
# 2 when a URL existed but every download attempt failed.
#
# The 1-vs-2 split is what makes the negative cache safe: 1 means upstream genuinely has no
# such artwork and is worth remembering, 2 means a transient network/CDN problem that must
# stay retryable. Caching a 2 would suppress a legitimate retry for LIDARR_ART_RECHECK_DAYS.
download_if_valid() {
    local url="$1"
    local dest="$2"

    [[ -z "$url" || "$url" == "null" ]] && return 1
    [[ -f "$dest" ]] && return 0

    if [[ "$DRY_RUN" == true ]]; then
        log "DRY RUN — would fetch: $(basename "$dest")"
        return 0
    fi

    local tmp="${dest}.tmp"
    local i
    for (( i=0; i<=LIDARR_ART_RETRIES; i++ )); do
        curl -s --connect-timeout 5 --max-time 20 -L -o "$tmp" "$url"
        local size
        size=$(stat -c%s "$tmp" 2>/dev/null || echo 0)
        if (( size > LIDARR_ART_MIN_SIZE )); then
            mv "$tmp" "$dest"
            log "  Fetched: $(basename "$dest")"
            return 0
        fi
        rm -f "$tmp"
        sleep 1
    done

    warn "Failed to fetch: $(basename "$dest")"
    return 2
}

# ── Negative art cache ────────────────────────────────────────────────────────────────────────
# Same shape as arr_corruption_scan.sh's clean-file skip cache: a flat TSV of "<key>\t<epoch>"
# in DATA_DIR, slurped into an assoc array once at startup.
#
# Keyed per (entity, artwork filename) so an album that got cover.jpg from iTunes but has no
# cdart upstream still caches the cdart miss alone. MBID is the key where present because it
# survives a Lidarr DB rebuild; albums with no MBID fall back to the Lidarr id.
art_key() {
    local mbid="$1" fallback_id="$2"
    if [[ -n "$mbid" && "$mbid" != "null" ]]; then echo "$mbid"; else echo "lidarrid:$fallback_id"; fi
}

art_miss_fresh() {
    local stamp="${ART_MISS[$1]:-}"
    [[ -z "$stamp" ]] && return 1
    (( NOW - stamp < ART_RECHECK_SECS ))
}

# True when every artwork target still missing from disk is a known-fresh upstream miss —
# i.e. this entity cannot possibly gain anything from another round of API calls right now.
# This is the check that skips the fanart.tv request entirely, which is where the time goes.
art_all_cached() {
    local dir="$1"; local key="$2"; shift 2
    local target
    [[ "$ART_REFRESH" == true ]] && return 1
    for target in "$@"; do
        [[ -f "$dir/$target" ]] && continue
        art_miss_fresh "${key}:${target}" || return 1
    done
    return 0
}

# Merges this run's fresh misses into the persistent cache, newest wins per key (fresh file is
# read first so awk's first-seen is always the newer stamp). Entries past the recheck window are
# dropped rather than carried: they would be re-queried on the next run anyway, so expiring them
# here is free and keeps the file from growing without bound as albums leave the library.
merge_art_misses() {
    local fresh="$1" tmp
    [[ "$DRY_RUN" == true ]] && return 0
    [[ -s "$fresh" ]] || return 0
    tmp=$(mktemp)
    cat "$fresh" "$LIDARR_ART_MISS_CACHE" 2>/dev/null |
        awk -F'\t' -v cutoff="$(( NOW - ART_RECHECK_SECS ))" \
            'NF==2 && !seen[$1]++ && $2 >= cutoff' | sort > "$tmp"
    mv "$tmp" "$LIDARR_ART_MISS_CACHE"
}

# Runs one single-source artwork target and classifies the outcome. Must be called from inside
# a fetch subshell — it updates that subshell's _fetches/_nourl/_dlfail/_miss_keys directly.
try_single() {
    local dest="$1" url="$2" key="$3" rc
    [[ -f "$dest" ]] && return 0
    download_if_valid "$url" "$dest"; rc=$?
    case "$rc" in
        0) (( _fetches++ )) ;;
        2) (( _dlfail++ ))  ;;
        *) (( _nourl++ )); _miss_keys+="${key}"$'\t'"${NOW}"$'\n' ;;
    esac
}

deezer_artist_image() {
    local artist="$1"
    local query
    query=$(printf "%s" "$artist" | sed 's/ /+/g')
    curl_json "https://api.deezer.com/search/artist?q=$query" |
        jq -r '.data[0].picture_xl // empty' 2>/dev/null
}

lastfm_artist_image() {
    local artist="$1"
    local encoded
    encoded=$(printf "%s" "$artist" | sed 's/ /%20/g')
    curl_json "https://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=$encoded&api_key=$LASTFM_API_KEY&format=json" |
        jq -r '.artist.image[-1]["#text"] // empty' 2>/dev/null
}

# ==============================================================================================
# ━━━ Verify Lidarr reachable ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_NET Lidarr API ━━━"

if ! curl_json "$LIDARR_URL/api/v1/system/status?apikey=$LIDARR_API_KEY" | jq -e '.version' >/dev/null 2>&1; then
    error "Lidarr API unreachable at $LIDARR_URL — aborting"
    notify "lidarr_missing_art failed — Lidarr API unreachable on $(hostname)" "Lidarr Missing Art" "warning"
    exit 1
fi
info "Lidarr reachable — $LIDARR_URL"

START=$(date +%s)

ALBUMS_CHECKED=0
ALBUMS_COMPLETE=0
ALBUMS_CACHED=0

ARTISTS_CHECKED=0
ARTISTS_COMPLETE=0
ARTISTS_CACHED=0

# ==============================================================================================
# ━━━ Build Album Directory Map ━━━
# ==============================================================================================
# Lidarr's album API never populates .path — derive album dirs from track file paths instead.
echo ""
echo "━━━ $ICON_SYNC Building Album Directory Map ━━━"

declare -A ALBUM_DIR_MAP
# Cache-first — arr_get_tracked_data() serves the shared cache when it's fresh (now kept
# current every 30min by arr_cache_prefill.sh in CRITICAL_MAINTENANCE_SCRIPTS), falls back to
# a live fetch when it's stale, and waits out an active rescan before either.
_artist_list=$(arr_get_tracked_data "lidarr" "$LIDARR_URL" "$LIDARR_API_KEY" "v1")
_map_artist_count=$(echo "$_artist_list" | jq '. | length')
info "Fetching track files for $_map_artist_count artists..."

# Cache-first for the per-track data too — lidarr_cleanup.sh (runs earlier in the same nightly
# window) already does this exact per-artist walk for its own cleanup decisions and writes the
# result through via arr_item_cache_write(). Read that instead of repeating the walk; fall
# back to the live per-artist walk below only if it's missing or from outside this window.
# See arr_item_cache_write()/arr_get_cached_items() in common.sh. (2026-07-17)
_cached_tracks=$(arr_get_cached_items "lidarr")
if [[ -n "$_cached_tracks" ]]; then
    info "Using cached track data from lidarr_cleanup.sh — skipping live per-artist walk"
    while IFS=$'\t' read -r _album_id _track_path; do
        [[ -z "$_album_id" || -z "$_track_path" || "$_track_path" == "null" ]] && continue
        # Parameter expansion instead of external dirname — this loop runs once per track
        # (127K+ on this library), and dirname forks a subprocess per call. Measured
        # 2026-07-17: ~185x faster (0.39s vs 72.2s for 20K calls) for the identical result.
        ALBUM_DIR_MAP["$_album_id"]="${_track_path%/*}"
    done < <(echo "$_cached_tracks" | jq -r '.[] | [(.albumId | tostring), .path] | @tsv' 2>/dev/null)
else
    while IFS= read -r _artist_id; do
        [[ -z "$_artist_id" ]] && continue
        while IFS=$'\t' read -r _album_id _track_path; do
            [[ -z "$_album_id" || -z "$_track_path" || "$_track_path" == "null" ]] && continue
            ALBUM_DIR_MAP["$_album_id"]="${_track_path%/*}"
        done < <(curl_json "$LIDARR_URL/api/v1/trackFile?artistId=${_artist_id}&apikey=$LIDARR_API_KEY" | \
            jq -r '.[] | [(.albumId | tostring), .path] | @tsv' 2>/dev/null)
    done < <(echo "$_artist_list" | jq -r '.[].id')
fi
unset _artist_list _map_artist_count _artist_id _album_id _track_path _cached_tracks

info "Mapped ${#ALBUM_DIR_MAP[@]} albums with local tracks"

# ==============================================================================================
# ━━━ Albums ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_EMBY Albums ━━━"

albums=$(curl_json "$LIDARR_URL/api/v1/album?apikey=$LIDARR_API_KEY")

if [[ -z "$albums" || "$albums" == "null" ]]; then
    error "Lidarr album API returned empty — aborting"
    notify "lidarr_missing_art failed — album API empty on $(hostname)" "Lidarr Missing Art" "warning"
    exit 1
fi

total_albums=$(echo "$albums" | jq '. | length')
info "Processing $total_albums albums..."

while IFS=$'\t' read -r mbid artist_name album_name album_id; do
    raw_dir="${ALBUM_DIR_MAP[$album_id]:-}"
    [[ -z "$raw_dir" ]] && continue  # not downloaded, skip
    local_path=$(translate_path "$raw_dir")
    (( ALBUMS_CHECKED++ ))

    [[ ! -d "$local_path" ]] && continue

    log "[$ALBUMS_CHECKED/$total_albums] $artist_name — $album_name"

    if [[ -f "$local_path/cover.jpg" &&
          -f "$local_path/cdart.png"  &&
          -f "$local_path/back.jpg"   ]]; then
        (( ALBUMS_COMPLETE++ ))
        log "  complete — skipping"
        continue
    fi

    akey=$(art_key "$mbid" "$album_id")

    if art_all_cached "$local_path" "$akey" cover.jpg cdart.png back.jpg; then
        (( ALBUMS_CACHED++ ))
        log "  every missing target is a known upstream miss — skipping"
        continue
    fi

    wait_for_slot

    (
        _fetches=0 _nourl=0 _dlfail=0 _miss_keys=""

        JSON=""
        if [[ -n "$mbid" && "$mbid" != "null" ]]; then
            JSON=$(curl_json "http://webservice.fanart.tv/v3/music/albums/$mbid?api_key=$FANART_API_KEY")
            sleep "$LIDARR_ART_SLEEP_BETWEEN"
        fi

        # cover.jpg has a two-source chain, so it tracks whether *any* source offered a URL:
        # only a clean no-URL-anywhere result is cacheable.
        if [[ ! -f "$local_path/cover.jpg" ]]; then
            _saw_url=false
            IMG=$(echo "$JSON" | jq -r '.albums[].albumcover[0].url // empty' 2>/dev/null)
            download_if_valid "$IMG" "$local_path/cover.jpg"; _rc=$?
            (( _rc == 2 )) && _saw_url=true
            if (( _rc == 0 )); then
                (( _fetches++ ))
            else
                query=$(printf "%s %s" "$artist_name" "$album_name" | sed 's/ /+/g')
                itunes=$(curl_json "https://itunes.apple.com/search?term=$query&entity=album&limit=1" |
                    jq -r '.results[0].artworkUrl100 // empty' 2>/dev/null | sed 's/100x100/600x600/')
                download_if_valid "$itunes" "$local_path/cover.jpg"; _rc=$?
                (( _rc == 2 )) && _saw_url=true
                if (( _rc == 0 )); then
                    (( _fetches++ ))
                elif [[ "$_saw_url" == true ]]; then
                    (( _dlfail++ ))
                else
                    (( _nourl++ )); _miss_keys+="${akey}:cover.jpg"$'\t'"${NOW}"$'\n'
                fi
            fi
        fi

        try_single "$local_path/cdart.png" \
            "$(echo "$JSON" | jq -r '.albums[].cdart[0].url // empty' 2>/dev/null)" \
            "${akey}:cdart.png"

        try_single "$local_path/back.jpg" \
            "$(echo "$JSON" | jq -r '.albums[].albumback[0].url // empty' 2>/dev/null)" \
            "${akey}:back.jpg"

        (( _fetches > 0 )) && printf '1\n' >> "$LIDARR_TMP/album_fetches"
        (( _nourl > 0 ))   && printf '1\n' >> "$LIDARR_TMP/album_nourl"
        (( _dlfail > 0 ))  && printf '1\n' >> "$LIDARR_TMP/album_dlfail"
        [[ -n "$_miss_keys" ]] && printf '%s' "$_miss_keys" >> "$LIDARR_TMP/album_misses"
    ) &

done < <(echo "$albums" | jq -r '.[] | [(.foreignAlbumId // ""), (.artist.artistName // ""), (.title // ""), (.id | tostring)] | @tsv')

wait

merge_art_misses "$LIDARR_TMP/album_misses"

ALBUM_FETCHED=$(wc -l < "$LIDARR_TMP/album_fetches" 2>/dev/null || echo 0)
ALBUM_NOART=$(wc -l   < "$LIDARR_TMP/album_nourl"   2>/dev/null || echo 0)
ALBUM_DLFAIL=$(wc -l  < "$LIDARR_TMP/album_dlfail"  2>/dev/null || echo 0)
ALBUM_MISSING=$(( ALBUMS_CHECKED - ALBUMS_COMPLETE ))
info "Checked: $ALBUMS_CHECKED | Complete: $ALBUMS_COMPLETE | Needed art: $ALBUM_MISSING | Cached-skip: $ALBUMS_CACHED | Fetched: $ALBUM_FETCHED | No art upstream: $ALBUM_NOART | Fetch failed: $ALBUM_DLFAIL"

# ==============================================================================================
# ━━━ Artists ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_EMBY Artists ━━━"

# Cache-first — see the earlier album-map fetch above for why (arr_get_tracked_data serves
# the shared cache when fresh, live fetch as fallback, waits out an active rescan first).
artists=$(arr_get_tracked_data "lidarr" "$LIDARR_URL" "$LIDARR_API_KEY" "v1")

if [[ -z "$artists" || "$artists" == "null" ]]; then
    error "Lidarr artist API returned empty — aborting"
    notify "lidarr_missing_art failed — artist API empty on $(hostname)" "Lidarr Missing Art" "warning"
    exit 1
fi

total_artists=$(echo "$artists" | jq '. | length')
info "Processing $total_artists artists..."

while IFS=$'\t' read -r local_path mbid name; do
    local_path=$(translate_path "$local_path")
    (( ARTISTS_CHECKED++ ))

    [[ ! -d "$local_path" ]] && continue
    [[ -z "$mbid" || "$mbid" == "null" ]] && continue

    log "[$ARTISTS_CHECKED/$total_artists] $name"

    if [[ -f "$local_path/folder.jpg" &&
          -f "$local_path/fanart.jpg"  &&
          -f "$local_path/clearlogo.png" &&
          -f "$local_path/banner.jpg"  ]]; then
        (( ARTISTS_COMPLETE++ ))
        log "  complete — skipping"
        continue
    fi

    if art_all_cached "$local_path" "$mbid" folder.jpg fanart.jpg clearlogo.png banner.jpg; then
        (( ARTISTS_CACHED++ ))
        log "  every missing target is a known upstream miss — skipping"
        continue
    fi

    wait_for_slot

    (
        _fetches=0 _nourl=0 _dlfail=0 _miss_keys=""

        JSON=$(curl_json "http://webservice.fanart.tv/v3/music/$mbid?api_key=$FANART_API_KEY")
        sleep "$LIDARR_ART_SLEEP_BETWEEN"

        # folder.jpg walks fanart → Deezer → Last.fm; only a no-URL result from all three is
        # cacheable, same reasoning as the album cover chain.
        if [[ ! -f "$local_path/folder.jpg" ]]; then
            _saw_url=false
            IMG=$(echo "$JSON" | jq -r '.artistthumb[0].url // empty' 2>/dev/null)
            download_if_valid "$IMG" "$local_path/folder.jpg"; _rc=$?
            (( _rc == 2 )) && _saw_url=true
            if (( _rc == 0 )); then
                (( _fetches++ ))
            else
                IMG=$(deezer_artist_image "$name")
                download_if_valid "$IMG" "$local_path/folder.jpg"; _rc=$?
                (( _rc == 2 )) && _saw_url=true
                if (( _rc == 0 )); then
                    (( _fetches++ ))
                else
                    IMG=$(lastfm_artist_image "$name")
                    download_if_valid "$IMG" "$local_path/folder.jpg"; _rc=$?
                    (( _rc == 2 )) && _saw_url=true
                    if (( _rc == 0 )); then
                        (( _fetches++ ))
                    elif [[ "$_saw_url" == true ]]; then
                        (( _dlfail++ ))
                    else
                        (( _nourl++ )); _miss_keys+="${mbid}:folder.jpg"$'\t'"${NOW}"$'\n'
                    fi
                fi
            fi
        fi

        if [[ ! -f "$local_path/fanart.jpg" ]]; then
            _saw_url=false
            IMG=$(echo "$JSON" | jq -r '.artistbackground[0].url // empty' 2>/dev/null)
            download_if_valid "$IMG" "$local_path/fanart.jpg"; _rc=$?
            (( _rc == 2 )) && _saw_url=true
            if (( _rc == 0 )); then
                (( _fetches++ ))
            else
                IMG=$(deezer_artist_image "$name")
                download_if_valid "$IMG" "$local_path/fanart.jpg"; _rc=$?
                (( _rc == 2 )) && _saw_url=true
                if (( _rc == 0 )); then
                    (( _fetches++ ))
                elif [[ "$_saw_url" == true ]]; then
                    (( _dlfail++ ))
                else
                    (( _nourl++ )); _miss_keys+="${mbid}:fanart.jpg"$'\t'"${NOW}"$'\n'
                fi
            fi
        fi

        try_single "$local_path/clearlogo.png" \
            "$(echo "$JSON" | jq -r '.hdmusiclogo[0].url // empty' 2>/dev/null)" \
            "${mbid}:clearlogo.png"

        try_single "$local_path/banner.jpg" \
            "$(echo "$JSON" | jq -r '.musicbanner[0].url // empty' 2>/dev/null)" \
            "${mbid}:banner.jpg"

        (( _fetches > 0 )) && printf '1\n' >> "$LIDARR_TMP/artist_fetches"
        (( _nourl > 0 ))   && printf '1\n' >> "$LIDARR_TMP/artist_nourl"
        (( _dlfail > 0 ))  && printf '1\n' >> "$LIDARR_TMP/artist_dlfail"
        [[ -n "$_miss_keys" ]] && printf '%s' "$_miss_keys" >> "$LIDARR_TMP/artist_misses"
    ) &

done < <(echo "$artists" | jq -r '.[] | [.path, .foreignArtistId, .artistName] | @tsv')

wait

merge_art_misses "$LIDARR_TMP/artist_misses"

ARTIST_FETCHED=$(wc -l < "$LIDARR_TMP/artist_fetches" 2>/dev/null || echo 0)
ARTIST_NOART=$(wc -l   < "$LIDARR_TMP/artist_nourl"   2>/dev/null || echo 0)
ARTIST_DLFAIL=$(wc -l  < "$LIDARR_TMP/artist_dlfail"  2>/dev/null || echo 0)
ARTIST_MISSING=$(( ARTISTS_CHECKED - ARTISTS_COMPLETE ))
info "Checked: $ARTISTS_CHECKED | Complete: $ARTISTS_COMPLETE | Needed art: $ARTIST_MISSING | Cached-skip: $ARTISTS_CACHED | Fetched: $ARTIST_FETCHED | No art upstream: $ARTIST_NOART | Fetch failed: $ARTIST_DLFAIL"

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY LIDARR MISSING ART SUMMARY ━━━━━"
echo "$ICON_HOST    Identity:  $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME    Duration:  $(format_duration $((END - START)))"
echo "$ICON_EMBY   Albums:    $ALBUMS_CHECKED checked | $ALBUMS_COMPLETE complete | $ALBUM_FETCHED fetched"
echo "$ICON_SKIP    Albums:    $ALBUMS_CACHED skipped (cached miss) | $ALBUM_NOART no art upstream | $ALBUM_DLFAIL fetch failed"
echo "$ICON_EMBY   Artists:   $ARTISTS_CHECKED checked | $ARTISTS_COMPLETE complete | $ARTIST_FETCHED fetched"
echo "$ICON_SKIP    Artists:   $ARTISTS_CACHED skipped (cached miss) | $ARTIST_NOART no art upstream | $ARTIST_DLFAIL fetch failed"
echo "$ICON_GEAR    Cache:     $([[ -f "$LIDARR_ART_MISS_CACHE" ]] && wc -l < "$LIDARR_ART_MISS_CACHE" || echo 0) known upstream misses | recheck every ${LIDARR_ART_RECHECK_DAYS}d"

if [[ "$DRY_RUN" == true ]]; then
    echo "$ICON_WARN    Status:    DRY RUN — no files written"
else
    echo "$ICON_DONE    Status:    $ICON_SUCCESS ALL DONE"
    notify "Lidarr art fetch complete on $(hostname) — ${ALBUMS_CHECKED} albums, ${ARTISTS_CHECKED} artists processed" "Lidarr Missing Art" "normal"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

exit 0
