#!/bin/bash

# Network-drift heal — the mutating half of the detector. Runs ONLY through the
# task system (see cli_system_commands.sh `network heal`, which enqueues unless
# LIBREPORTAL_TASK_EXEC=1), never a direct API.
#
# It (1) realigns CFG to docker's real subnet IN-PROCESS so re-IP draws from the
# corrected /24, then (2) re-IPs each stranded app with ports PRESERVED
# (reset_network="ip"), healing a gateway provider (gluetun) first so recreating
# it doesn't orphan the apps routed through it. Re-IP runs sequentially, and a
# fresh scan afterwards rewrites network_status.json so the WebUI badge clears
# (or stays, if anything failed to heal).
#
#   networkHealConflicts [app]   # heal one app, or all detected conflicts
networkHealConflicts() {
    local target_app="$1"
    isHeader "Healing network drift"

    # 1) Realign CFG to docker's real subnet in this process (ipFindAvailable
    #    reads CFG_NETWORK_SUBNET, so this must happen before any re-IP).
    local docker_subnet
    docker_subnet=$(dockerCommandRun "docker network inspect $CFG_NETWORK_NAME --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'" 2>/dev/null | tr -d '[:space:]')
    if [[ -z "$docker_subnet" ]]; then
        isNotice "Network '$CFG_NETWORK_NAME' not present — (re)creating it."
        DOCKER_NETWORK_SETUP_NEEDED="true"
        declare -f installDockerNetwork >/dev/null 2>&1 && installDockerNetwork
        docker_subnet=$(dockerCommandRun "docker network inspect $CFG_NETWORK_NAME --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'" 2>/dev/null | tr -d '[:space:]')
    fi
    if [[ -n "$docker_subnet" && "$docker_subnet" != "$CFG_NETWORK_SUBNET" ]]; then
        declare -f adoptDockerSubnet >/dev/null 2>&1 && adoptDockerSubnet "$docker_subnet"
    fi

    # 2) Build the app set (unique). Either the requested app, or a fresh scan.
    local -a apps=() a
    if [[ -n "$target_app" ]]; then
        apps=("$target_app")
    else
        networkScanConflicts   # populates NET_CONFLICTS (call direct, not $(...))
        local row seen=""
        for row in "${NET_CONFLICTS[@]}"; do
            a="${row%%|*}"
            [[ -n "$a" ]] || continue
            # de-dupe app names (an app can have several drifted services)
            [[ " $seen " == *" $a "* ]] && continue
            seen+=" $a"
            apps+=("$a")
        done
    fi

    if [[ ${#apps[@]} -eq 0 ]]; then
        isSuccessful "No network conflicts to heal."
        declare -f webuiSystemNetworkCheck >/dev/null 2>&1 && webuiSystemNetworkCheck "force" >/dev/null 2>&1
        return 0
    fi

    # 3) Heal a gateway PROVIDER first — recreating gluetun re-attaches every app
    #    routed through it, so it must settle before (or be reconciled after) them.
    local -a ordered=()
    for a in "${apps[@]}"; do [[ "$a" == "gluetun" ]] && ordered+=("$a"); done
    for a in "${apps[@]}"; do [[ "$a" != "gluetun" ]] && ordered+=("$a"); done

    # 4) Re-IP each (IP-only — ports preserved), sequentially.
    local attempted=0
    for a in "${ordered[@]}"; do
        if [[ ! "$a" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then
            isError "Skipping invalid app slug: $a"; continue
        fi
        isNotice "Re-IPing '$a' into ${CFG_NETWORK_SUBNET} (ports preserved)…"
        dockerInstallApp "$a" "" ip
        ((attempted++))
    done

    # 5) Reconcile gateway-routed apps onto the (possibly recreated) provider.
    declare -f appGluetunRecreateRouted >/dev/null 2>&1 && appGluetunRecreateRouted >/dev/null 2>&1 || true

    # 6) Fresh scan rewrites the status file; report what (if anything) remains.
    if declare -f webuiSystemNetworkCheck >/dev/null 2>&1; then
        webuiSystemNetworkCheck "force" >/dev/null 2>&1
    fi
    networkScanConflicts
    local remaining=${#NET_CONFLICTS[@]}

    if (( remaining > 0 )); then
        isError "Network heal attempted ${attempted} app(s); ${remaining} conflict(s) still detected — re-run or inspect manually."
        return 1
    fi
    isSuccessful "Network heal complete — re-IP'd ${attempted} app(s); no conflicts remain."
}
