#!/bin/bash
# ==============================================================================================
# ================================= Docker Network Connect =====================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Ensures custom Docker networks exist and connects configured containers to
# them at every array start.
#
# Called via ARRAY_START_SCRIPTS — runs early in the array start sequence,
# before watchdogs begin their first cycle. Idempotent: safe to re-run at any
# time. Silent when everything is already correct. Notifies when a network had
# to be created — that only happens after a unRAID update wipes custom networks,
# and it is worth knowing when it does.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# For each configured network:
#
#   1. Does the network exist?
#        NO  → create it (bridge driver, Docker assigns subnet automatically)
#             → send notification — creation is unexpected outside post-update recovery
#        YES → skip silently
#
#   2. For each configured container:
#        Already connected  → skip silently
#        Not connected      → connect it
#        Container missing  → warn and skip — may not be running yet, not fatal
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Idempotent by Design
#   Running this script multiple times produces the same result as running it
#   once. Skips anything already in the correct state without error or noise.
#
# Silent When Correct
#   Produces no output on a clean run. The absence of output is confirmation
#   that everything is already correct.
#
# Notify on Creation
#   Network creation is always notified because it should only happen after a
#   unRAID update. If it happens regularly something is misconfigured and the
#   operator needs to know.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Docker network operations require root privileges.
#
# Docker Presence Check
#   Verifies the docker binary exists before any network operations.
#
# Lock Acquisition
#   Prevents concurrent execution via acquire_lock(). Safe to call from
#   array start hooks or manually without risk of overlap.
#
# Host Detection
#   detect_hosts() identifies which server is running the script and aliases
#   HOST*_NETWORK_CONNECT_* arrays to the correct host's values.
#
# Docker Daemon Check
#   Verifies daemon is responsive before any network operations. Network
#   commands against a hung daemon hang indefinitely.
#
# Timeout Protection
#   All docker commands wrapped in timeout. Daemon hangs cannot stall the
#   array start sequence.
#
# Empty Array Guards
#   Warns and exits cleanly if NETWORK_CONNECT_NETWORKS or
#   NETWORK_CONNECT_CONTAINERS are unconfigured.
#
# Missing Container Tolerance
#   A configured container that does not exist yet warns and is skipped rather
#   than failing the run. This script executes early at array start, before
#   every container has necessarily been created.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# host*.conf
#
#   HOST*_NETWORK_CONNECT_NETWORKS
#       Networks to ensure exist at array start. Aliased by detect_hosts() →
#       NETWORK_CONNECT_NETWORKS
#
#   HOST*_NETWORK_CONNECT_CONTAINERS
#       Containers to connect to every configured network. Aliased by
#       detect_hosts() → NETWORK_CONNECT_CONTAINERS
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# docker_network_connect.sh
#     Ensure all configured networks exist and containers are connected
#
# docker_network_connect.sh --dry-run
#     Preview what would be created or connected without making changes
#
# docker_network_connect.sh --status
#     Show current network state and container connection status
#
# docker_network_connect.sh --log
#     Verbose per-network per-container output
#
# ==============================================================================================

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

acquire_lock

if ! command -v docker &>/dev/null; then
    error "Docker not found — cannot manage networks"
    exit 1
fi

# detect_hosts() sets MY_ID and aliases HOST*_NETWORK_CONNECT_* arrays
detect_hosts

# Docker daemon check — network operations are useless if daemon is hung
DOCKER_TIMEOUT=15
if ! timeout "$DOCKER_TIMEOUT" docker info >/dev/null 2>&1; then
    error "Docker daemon not responding — cannot manage networks"
    notify "docker_network_connect failed on $(hostname) — Docker daemon not responding" \
        "Network Connect" "warning"
    exit 1
fi

# Empty array guards
if [[ ${#NETWORK_CONNECT_NETWORKS[@]} -eq 0 ]]; then
    warn "NETWORK_CONNECT_NETWORKS is empty for $MY_ID — nothing to do"
    warn "Check HOST*_NETWORK_CONNECT_NETWORKS in host*.conf"
    exit 0
fi

if [[ ${#NETWORK_CONNECT_CONTAINERS[@]} -eq 0 ]]; then
    warn "NETWORK_CONNECT_CONTAINERS is empty for $MY_ID — no containers to connect"
    warn "Check HOST*_NETWORK_CONNECT_CONTAINERS in host*.conf"
    exit 0
fi

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
    echo "$ICON_HOST        Identity:   $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_DOCKER_NET  Networks:   ${NETWORK_CONNECT_NETWORKS[*]}"
    echo "$ICON_CONTAINERS  Containers: ${NETWORK_CONNECT_CONTAINERS[*]}"
    echo ""
    echo "━━━ Network State ━━━"
    for network in "${NETWORK_CONNECT_NETWORKS[@]}"; do
        [[ -z "$network" ]] && continue
        if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" &>/dev/null; then
            echo "  $ICON_SUCCESS $network — exists"
            timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \
                --format '  Subnet: {{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null
        else
            echo "  $ICON_ERROR $network — missing (will be created on next run)"
        fi
    done
    echo ""
    echo "━━━ Container Connections ━━━"
    for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do
        [[ -z "$container" ]] && continue
        echo "$ICON_CONTAINERS $container:"
        timeout "$DOCKER_TIMEOUT" docker inspect "$container" \
            --format '{{range $k, $v := .NetworkSettings.Networks}}    {{$k}}{{"\\n"}}{{end}}' \
            2>/dev/null || echo "    not found"
    done
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ==============================================================================================
# ━━━ Network Ensure + Connect ━━━
# ==============================================================================================
log "Networks:   ${NETWORK_CONNECT_NETWORKS[*]}"
log "Containers: ${NETWORK_CONNECT_CONTAINERS[*]}"

START=$(date +%s)
NETWORKS_CREATED=()
CONNECTED=()
SKIPPED=()
FAILED=()

for network in "${NETWORK_CONNECT_NETWORKS[@]}"; do
    [[ -z "$network" ]] && continue

    log "Processing network: $network"

    # ── Step 1 — ensure network exists ───────────────────────────────────────────────────────
    if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" &>/dev/null; then
        net_subnet=""
        net_driver=""
        net_subnet=$(timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \
            --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null || echo "unknown")
        net_driver=$(timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \
            --format '{{.Driver}}' 2>/dev/null || echo "unknown")
        log "$ICON_DOCKER_NET $network exists ✅ — driver: $net_driver subnet: $net_subnet"
    else
        warn "$ICON_DOCKER_NET $network not found — creating (unRAID update may have wiped networks)"
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would create: $network (bridge, auto subnet)"
        else
            if timeout "$DOCKER_TIMEOUT" docker network create \
                --driver bridge "$network" >/dev/null 2>&1; then
                warn "$ICON_DOCKER_NET $network created"
                NETWORKS_CREATED+=("$network")
                # Network creation is unexpected — notify so user is aware
                notify "Docker network created on $(hostname) — $network (unRAID update likely wiped it)" \
                    "Network Connect" "normal"
            else
                error "$network — failed to create"
                FAILED+=("$network:create")
                continue
            fi
        fi
    fi

    # ── Step 2 — connect containers to this network ──────────────────────────────────────────
    for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do
        [[ -z "$container" ]] && continue

        # Container not found — warn and skip, not an error
        if ! timeout "$DOCKER_TIMEOUT" docker inspect "$container" &>/dev/null; then
            warn "$container not found — skipping (may not be running yet)"
            continue
        fi

        # Already connected — skip cleanly and silently
        if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \
            --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null \
            | grep -qw "$container"; then
            log "$container already on $network — skipping"
            SKIPPED+=("$container→$network")
            continue
        fi

        # Connect
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would connect $container to $network"
            continue
        fi

        log "Connecting $container to $network..."
        if timeout "$DOCKER_TIMEOUT" docker network connect "$network" "$container" 2>/dev/null; then
            log "$container connected to $network"
            CONNECTED+=("$container→$network")
        else
            error "Failed to connect $container to $network"
            FAILED+=("$container:$network")
        fi
    done

done

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
# Always show summary — this runs at array start and output is useful for diagnostics
echo ""
echo "━━━━━ $ICON_SUMMARY NETWORK CONNECT SUMMARY ━━━━━"
echo "$ICON_HOST       Identity:  $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME       Duration:  $(format_duration $((END - START)))"
[[ ${#NETWORKS_CREATED[@]} -gt 0 ]] && warn "$ICON_DOCKER_NET Created:   ${NETWORKS_CREATED[*]} (networks were missing)"
[[ ${#CONNECTED[@]} -gt 0 ]]        && log "Connected: ${CONNECTED[*]}"
[[ ${#SKIPPED[@]} -gt 0 ]]          && log "Already connected: ${SKIPPED[*]}"
[[ ${#FAILED[@]} -gt 0 ]]           && echo "$ICON_ERROR      Failed:    ${FAILED[*]}"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -gt 0 ]]; then
    echo "$ICON_ERROR Status: SOME OPERATIONS FAILED"
    notify "Docker network connect failed on $(hostname) — ${FAILED[*]}" \
        "Network Connect" "warning"
elif [[ ${#NETWORKS_CREATED[@]} -gt 0 ]]; then
    warn "Networks recreated — ${NETWORKS_CREATED[*]} — unRAID update likely wiped them"
else
    echo "All networks healthy — no action needed"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

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