#!/bin/bash
# ==============================================================================================
# =============================== Emby → Sonarr Sync ==========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# One-shot tool. Finds TV series present in Emby that are not tracked in Sonarr
# and adds them. No scoring — if it's in your library, Sonarr should monitor it.
#
# Intended as a bootstrap / catch-up tool. Run after Sonarr setup, after a
# database wipe, or any time you suspect gaps between your library and Sonarr.
#
# Cache-first Sonarr library fetch (2026-07-17) — 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.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
#   1. Fetch all Series items from Emby (with TVDB provider IDs)
#   2. Fetch current Sonarr library (indexed by TVDB ID and title)
#   3. For each Emby series not in Sonarr → add to Sonarr
#
# Matching prefers TVDB ID when available (immune to title differences),
# falling back to case-insensitive title comparison.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Add-Only, Never Remove
#   The tool only adds series the arr is missing. It never deletes or unmonitors anything,
#   so the worst outcome of a bad match is an extra tracked entry — trivially reversible —
#   rather than lost tracking on something already curated.
#
# Library Membership Is the Whole Criterion
#   No scoring, no thresholds, no metadata quality gates. If it is in Emby, the arr should
#   know about it. This is deliberately not a discovery tool; the playback_aware_* scripts
#   own that job and its judgement calls.
#
# Provider ID Over Title
#   Matching prefers the TVDB ID and falls back to case-insensitive title only when the ID
#   is absent. Titles differ across sources by punctuation, year suffixes and articles;
#   matching on them alone would re-add things the arr already tracks.
#
# Cache-First Read
#   The arr library comes from the shared tracked-data cache, kept warm by
#   arr_cache_prefill.sh, with a live fetch as fallback. One read regardless of library size.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   acquire_lock requires root. Script exits cleanly if not root.
#
# Dry-Run Mode
#   --dry-run shows all series that would be added without making any Sonarr API calls.
#   Always run first when closing the gap after a fresh Sonarr install or database wipe.
#
# Add-Only
#   Only adds series to Sonarr. Items already tracked by TVDB ID or title are skipped
#   without modification. Safe to run multiple times — the second run finds nothing to add.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
#   SONARR_EMBY_LIBRARIES                      — Emby library names to scan (master.conf); empty = all libraries
#   HOST*_SONARR_URL / HOST*_SONARR_API_KEY    — Sonarr connection (aliased by detect_hosts)
#   HOST*_EMBY_URL   / HOST*_EMBY_API_KEY      — Emby connection (aliased by detect_hosts)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   emby_to_sonarr_sync.sh             — add all untracked series to Sonarr
#   emby_to_sonarr_sync.sh --dry-run   — show what would be added, no changes
#   emby_to_sonarr_sync.sh --log       — verbose output
#
# ==============================================================================================

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

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

parse_args "$@"

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

acquire_lock
detect_hosts

if ! command -v jq >/dev/null 2>&1; then
    error "jq not installed"
    exit 1
fi

if [[ -z "${EMBY_URL:-}" || -z "${EMBY_API_KEY:-}" ]]; then
    error "EMBY_URL / EMBY_API_KEY not configured — check host*.conf"
    exit 1
fi

if [[ -z "${SONARR_URL:-}" || -z "${SONARR_API_KEY:-}" ]]; then
    error "SONARR_URL / SONARR_API_KEY not configured — check host*.conf"
    exit 1
fi

log "$ICON_GEAR Config: emby=${EMBY_URL} sonarr=${SONARR_URL}"
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no series will be added to Sonarr"

# ==============================================================================================
# ── API HELPERS ───────────────────────────────────────────────────────────────────────────────
# ==============================================================================================


_sonarr_get() {
    curl -sf --max-time 30 \
        -H "X-Api-Key: $SONARR_API_KEY" \
        "${SONARR_URL}/api/v3/${1}" 2>/dev/null
}

_sonarr_lookup() {
    curl -sf --max-time 20 --get \
        --data-urlencode "term=$1" \
        -H "X-Api-Key: $SONARR_API_KEY" \
        "${SONARR_URL}/api/v3/series/lookup" 2>/dev/null
}

_sonarr_post() {
    curl -sf --max-time 20 -X POST \
        -H "X-Api-Key: $SONARR_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$1" \
        "${SONARR_URL}/api/v3/series" 2>/dev/null
}

_sonarr_command() {
    curl -sf --max-time 20 -X POST \
        -H "X-Api-Key: $SONARR_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$1" \
        "${SONARR_URL}/api/v3/command" 2>/dev/null
}

# Garbage title filter — catches multi-season folder names and bare fansub tags.
_is_garbage_title() {
    local t="$1"
    [[ "$t" =~ S[0-9]{2}-S[0-9]{2} ]]          && return 0  # Fruits Basket S01-S03
    [[ "$t" =~ ^\[.*\]$ ]]                       && return 0  # [Prof] bare bracket tag
    [[ "$t" == *$'\xef\xbf\xbd'* || "$t" == *"?"* ]] && return 0  # encoding corruption
    return 1
}

# ==============================================================================================
# ━━━ Fetch Emby Series Library ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY Emby → Sonarr Sync — $(date '+%Y-%m-%d %H:%M:%S') ━━━━━"
echo "$ICON_HOST $MY_ID ($LOCAL_SERVER_NAME)"
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no series will be added"

echo ""
echo "━━━ $ICON_SYNC Emby Series Library ━━━"

declare -A EMBY_SERIES  # name → tvdb_id (empty string if none)

if [[ "${#SONARR_EMBY_LIBRARIES[@]}" -gt 0 ]]; then
    LIBRARIES_JSON=$(emby_api "Library/VirtualFolders") || { error "Could not fetch Emby libraries"; exit 1; }
    for lib_name in "${SONARR_EMBY_LIBRARIES[@]}"; do
        lib_id=$(echo "$LIBRARIES_JSON" | jq -r --arg n "$lib_name" '.[] | select(.Name == $n) | .ItemId' 2>/dev/null)
        if [[ -z "$lib_id" ]]; then
            warn "Emby library not found: $lib_name"
            continue
        fi
        log "Scanning library: $lib_name (ItemId: $lib_id)"
        LIB_JSON=$(emby_api "Items?ParentId=${lib_id}&IncludeItemTypes=Series&Recursive=true&Fields=ProviderIds&Limit=5000") || {
            warn "Could not fetch series from library: $lib_name"
            continue
        }
        while IFS='|' read -r name tvdb_id; do
            [[ -z "$name" || "$name" == "null" ]] && continue
            _is_garbage_title "$name" && { log "  $ICON_SKIP Skipping garbage title: $name"; continue; }
            EMBY_SERIES["$name"]="$tvdb_id"
        done < <(echo "$LIB_JSON" | jq -r '.Items[] | [.Name, (.ProviderIds.Tvdb // "")] | join("|")' 2>/dev/null)
    done
else
    log "SONARR_EMBY_LIBRARIES empty — scanning all Emby libraries"
    ALL_JSON=$(emby_api "Items?IncludeItemTypes=Series&Recursive=true&Fields=ProviderIds&Limit=5000") || {
        error "Could not fetch Emby series"
        exit 1
    }
    while IFS='|' read -r name tvdb_id; do
        [[ -z "$name" || "$name" == "null" ]] && continue
        _is_garbage_title "$name" && { log "  $ICON_SKIP Skipping garbage title: $name"; continue; }
        EMBY_SERIES["$name"]="$tvdb_id"
    done < <(echo "$ALL_JSON" | jq -r '.Items[] | [.Name, (.ProviderIds.Tvdb // "")] | join("|")' 2>/dev/null)
fi

EMBY_COUNT=${#EMBY_SERIES[@]}
log "$EMBY_COUNT series in Emby library"

if [[ "$EMBY_COUNT" -eq 0 ]]; then
    warn "No series found in Emby library"
    exit 0
fi

# ==============================================================================================
# ━━━ Fetch Sonarr Library ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_SYNC Sonarr Library ━━━"

# Cache-first — arr_get_tracked_data() serves the shared cache when it's fresh (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.
SONARR_SERIES_JSON=$(arr_get_tracked_data "sonarr" "$SONARR_URL" "$SONARR_API_KEY" "v3") || { error "Could not fetch Sonarr series"; exit 1; }

declare -A SONARR_TVDB    # tvdb_id → 1
declare -A SONARR_TITLES  # lower(title) → 1

while IFS='|' read -r title tvdb_id; do
    [[ -n "$title" ]] && SONARR_TITLES["${title,,}"]=1
    [[ -n "$tvdb_id" && "$tvdb_id" != "0" ]] && SONARR_TVDB["$tvdb_id"]=1
done < <(echo "$SONARR_SERIES_JSON" | jq -r '.[] | [.title, (.tvdbId // 0 | tostring)] | join("|")' 2>/dev/null)

SONARR_COUNT=${#SONARR_TITLES[@]}
log "$SONARR_COUNT series already in Sonarr"

# ==============================================================================================
# ━━━ Find Gaps ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_GEAR Comparing Libraries ━━━"

declare -A MISSING  # name → tvdb_id
ALREADY=0

for name in "${!EMBY_SERIES[@]}"; do
    tvdb_id="${EMBY_SERIES[$name]}"

    if [[ -n "$tvdb_id" && "${SONARR_TVDB[$tvdb_id]+x}" ]]; then
        (( ALREADY++ ))
        log "  $ICON_SKIP Already tracked (TVDB $tvdb_id): $name"
        continue
    fi

    if [[ "${SONARR_TITLES[${name,,}]+x}" ]]; then
        (( ALREADY++ ))
        log "  $ICON_SKIP Already tracked (title): $name"
        continue
    fi

    MISSING["$name"]="$tvdb_id"
    log "  $ICON_WARN Not in Sonarr: $name${tvdb_id:+ (TVDB: $tvdb_id)}"
done

IFS=$'\n' SORTED_MISSING=($(printf '%s\n' "${!MISSING[@]}" | sort))

echo "  Already tracked: $ALREADY | Missing from Sonarr: ${#MISSING[@]}"

if [[ "${#MISSING[@]}" -eq 0 ]]; then
    echo "Sonarr already tracks everything in Emby"
    exit 0
fi

echo ""
echo "━━━ $ICON_SUMMARY Series to add (${#MISSING[@]}) ━━━"
for name in "${SORTED_MISSING[@]}"; do
    _tvdb="${MISSING[$name]}"
    log "  $name${_tvdb:+ (TVDB: $_tvdb)}"
done

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN complete — run without --dry-run to add these series"
    exit 0
fi

# ==============================================================================================
# ━━━ Add to Sonarr ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_SYNC Adding to Sonarr ━━━"

SONARR_ROOT=$(_sonarr_get "rootfolder" | jq -r 'first(.[] | select(.accessible == true)) | .path' 2>/dev/null)
QUALITY_ID=$(_sonarr_get "qualityprofile" | jq -r '.[0].id' 2>/dev/null)

if [[ -z "$SONARR_ROOT" ]]; then
    error "Could not determine Sonarr root folder"
    exit 1
fi
log "Root folder: $SONARR_ROOT | Quality profile: $QUALITY_ID"

ADDED=0
FAILED=0

for name in "${SORTED_MISSING[@]}"; do
    tvdb_id="${MISSING[$name]}"

    if [[ -n "$tvdb_id" ]]; then
        LOOKUP=$(_sonarr_lookup "tvdb:$tvdb_id")
    else
        LOOKUP=$(_sonarr_lookup "$name")
    fi

    if [[ -z "$LOOKUP" ]] || echo "$LOOKUP" | jq -e '. == [] or . == null' >/dev/null 2>&1; then
        warn "  $ICON_WARN No match in Sonarr lookup: $name"
        (( FAILED++ ))
        continue
    fi

    SERIES_DATA=$(echo "$LOOKUP" | jq '.[0]' 2>/dev/null)
    SONARR_TITLE=$(echo "$SERIES_DATA" | jq -r '.title // ""' 2>/dev/null)

    if [[ -z "$SONARR_TITLE" ]]; then
        warn "  $ICON_WARN Empty result for: $name"
        (( FAILED++ ))
        continue
    fi

    PAYLOAD=$(echo "$SERIES_DATA" | jq \
        --arg root "$SONARR_ROOT" \
        --argjson qid "$QUALITY_ID" \
        '. + {
            rootFolderPath: $root,
            qualityProfileId: $qid,
            monitored: true,
            seasonFolder: true,
            addOptions: {
                monitor: "all",
                searchForMissingEpisodes: false
            }
        }' 2>/dev/null)

    RESULT=$(_sonarr_post "$PAYLOAD")

    if echo "$RESULT" | jq -e '.id' >/dev/null 2>&1; then
        SERIES_ID=$(echo "$RESULT" | jq -r '.id')
        _sonarr_command "{\"name\":\"SeriesSearch\",\"seriesId\":${SERIES_ID}}" >/dev/null
        log "  $ICON_DONE Added: $SONARR_TITLE"
        (( ADDED++ ))
    else
        warn "  $ICON_WARN Failed to add: $name"
        log "  $(echo "$RESULT" | head -c 200)"
        (( FAILED++ ))
    fi
done

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY SYNC COMPLETE ━━━━━"
echo "  $ICON_DONE  Added:   $ADDED"
[[ "$FAILED" -gt 0 ]] && echo "  $ICON_WARN  Failed:  $FAILED"
echo "  $ICON_SKIP  Already tracked: $ALREADY"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

[[ "$ADDED" -gt 0 ]] && notify \
    "$ADDED series added to Sonarr from Emby library on $(hostname)" \
    "Emby → Sonarr Sync" "normal"

exit 0
