#!/bin/bash
################################################################################
# Script Name: dhcp-lease-exporter.sh
# Version: 1.01
# Description: Prometheus exporter for DHCP lease metrics — pool utilization,
#              active leases per subnet, lease expirations, reservation status,
#              DORA packet counts, and lease duration tracking for ISC DHCP
#              (dhcpd) and ISC Kea.
#
# Author:  Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
#
# Usage:
#   # Output to stdout
#   sudo ./dhcp-lease-exporter.sh
#
#   # Textfile collector mode
#   sudo ./dhcp-lease-exporter.sh --textfile
#
#   # HTTP server mode
#   sudo ./dhcp-lease-exporter.sh --http
#
#   # Custom port
#   sudo ./dhcp-lease-exporter.sh --http --port 9533
#
################################################################################

set -euo pipefail

# ============================================================================
# CONFIGURATION
# ============================================================================

readonly VERSION="1.0"
readonly SCRIPT_NAME="${0##*/}"

# DHCP backend — auto, dhcpd, or kea
DHCP_BACKEND="auto"

# dhcpd paths
DHCPD_LEASES="/var/lib/dhcp/dhcpd.leases"
DHCPD_CONF="/etc/dhcp/dhcpd.conf"

# Kea paths and API
KEA_LEASES="/var/lib/kea/kea-leases4.csv"
KEA_API="http://127.0.0.1:8000"
KEA_USE_API="true"

# Output settings
TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector"
HTTP_PORT=9533
LOCK_FILE="/tmp/dhcp-lease-exporter.lock"

# Runtime
MODE="stdout"
ONCE=false
DETECTED_BACKEND=""

# ============================================================================
# COLORS
# ============================================================================

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

log_info()  { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
log_warn()  { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }

show_usage() {
    cat <<EOF
Usage: sudo $SCRIPT_NAME [OPTIONS]

Export DHCP lease metrics as Prometheus-compatible text format.

MODES:
    --textfile      Write to node_exporter textfile collector
    --http          Run as standalone HTTP server
    (default)       Print to stdout

OPTIONS:
    --port PORT         HTTP port (default: $HTTP_PORT)
    --backend TYPE      Force backend: dhcpd or kea (default: auto)
    --once              In HTTP mode, serve one request and exit
    -h, --help          Show this help message

CONFIGURATION:
    Edit the variables at the top of this script to set paths, ports, etc.

EOF
    exit 0
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --textfile)   MODE="textfile"; shift ;;
            --http)       MODE="http"; shift ;;
            --port)       HTTP_PORT="$2"; shift 2 ;;
            --backend)    DHCP_BACKEND="$2"; shift 2 ;;
            --once)       ONCE=true; shift ;;
            -h|--help)    show_usage ;;
            *)            log_error "Unknown option: $1"; show_usage ;;
        esac
    done
}

write_metric_header() {
    local name="$1" type="$2" help="$3"
    echo "# HELP ${name} ${help}"
    echo "# TYPE ${name} ${type}"
}

acquire_lock() {
    if [ -f "$LOCK_FILE" ]; then
        local pid
        pid=$(cat "$LOCK_FILE" 2>/dev/null || true)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            log_error "Another instance is running (PID $pid)"
            exit 1
        fi
        rm -f "$LOCK_FILE"
    fi
    echo $$ > "$LOCK_FILE"
    trap 'rm -f "$LOCK_FILE"' EXIT
}

# ============================================================================
# BACKEND DETECTION
# ============================================================================

detect_backend() {
    if [ "$DHCP_BACKEND" != "auto" ]; then
        DETECTED_BACKEND="$DHCP_BACKEND"
        return
    fi

    if systemctl is-active --quiet isc-kea-dhcp4-server 2>/dev/null || \
       systemctl is-active --quiet kea-dhcp4 2>/dev/null; then
        DETECTED_BACKEND="kea"
    elif systemctl is-active --quiet isc-dhcp-server 2>/dev/null || \
         systemctl is-active --quiet dhcpd 2>/dev/null; then
        DETECTED_BACKEND="dhcpd"
    elif [ -f "$KEA_LEASES" ]; then
        DETECTED_BACKEND="kea"
    elif [ -f "$DHCPD_LEASES" ]; then
        DETECTED_BACKEND="dhcpd"
    else
        DETECTED_BACKEND="unknown"
    fi
}

# ============================================================================
# DHCPD FUNCTIONS
# ============================================================================

# Parse dhcpd.conf for subnet definitions and pool ranges
parse_dhcpd_subnets() {
    local conf="$DHCPD_CONF"
    [ -f "$conf" ] || return

    local current_subnet="" current_name="" range_start="" range_end=""
    local in_subnet=false

    while IFS= read -r line; do
        # Match subnet declaration
        if [[ "$line" =~ ^[[:space:]]*subnet[[:space:]]+([0-9.]+)[[:space:]]+netmask[[:space:]]+([0-9.]+) ]]; then
            current_subnet="${BASH_REMATCH[1]}"
            local netmask="${BASH_REMATCH[2]}"
            current_name="$current_subnet"
            in_subnet=true
            range_start=""
            range_end=""
            # Calculate CIDR from netmask
            local cidr
            cidr=$(netmask_to_cidr "$netmask")
            current_subnet="${current_subnet}/${cidr}"
        fi

        # Check for comment-based name
        if $in_subnet && [[ "$line" =~ ^[[:space:]]*#[[:space:]]*(.+) ]]; then
            if [ "$current_name" = "${current_subnet%%/*}" ]; then
                current_name="${BASH_REMATCH[1]}"
            fi
        fi

        # Match range declaration
        if $in_subnet && [[ "$line" =~ ^[[:space:]]*range[[:space:]]+([0-9.]+)[[:space:]]+([0-9.]+) ]]; then
            range_start="${BASH_REMATCH[1]}"
            range_end="${BASH_REMATCH[2]}"
        fi

        # End of subnet block
        if $in_subnet && [[ "$line" =~ ^[[:space:]]*\} ]]; then
            if [ -n "$range_start" ] && [ -n "$range_end" ]; then
                local total
                total=$(ip_range_count "$range_start" "$range_end")
                echo "${current_subnet}|${current_name}|${total}|${range_start}|${range_end}"
            fi
            in_subnet=false
        fi
    done < "$conf"
}

netmask_to_cidr() {
    local netmask="$1"
    local cidr=0
    for octet in $(echo "$netmask" | tr '.' ' '); do
        case $octet in
            255) cidr=$((cidr + 8)) ;;
            254) cidr=$((cidr + 7)) ;;
            252) cidr=$((cidr + 6)) ;;
            248) cidr=$((cidr + 5)) ;;
            240) cidr=$((cidr + 4)) ;;
            224) cidr=$((cidr + 3)) ;;
            192) cidr=$((cidr + 2)) ;;
            128) cidr=$((cidr + 1)) ;;
            0)   ;;
        esac
    done
    echo "$cidr"
}

ip_to_int() {
    local a b c d
    IFS='.' read -r a b c d <<< "$1"
    echo $(( (a << 24) + (b << 16) + (c << 8) + d ))
}

ip_range_count() {
    local start_int end_int
    start_int=$(ip_to_int "$1")
    end_int=$(ip_to_int "$2")
    echo $(( end_int - start_int + 1 ))
}

# Count active leases per subnet from dhcpd.leases
count_dhcpd_leases() {
    local lease_file="$DHCPD_LEASES"
    [ -f "$lease_file" ] || return

    local now
    now=$(date +%s)

    awk -v now="$now" '
    /^lease / { ip = $2 }
    /ends / {
        gsub(/[;\/:]/, " ", $0)
        if ($2 != "never") {
            t = mktime($3 " " $4 " " $5 " " $6 " " $7 " " $8)
            if (t > now) active[ip] = t - now
        }
    }
    /binding state active/ { state[ip] = "active" }
    END {
        for (ip in active) {
            if (state[ip] == "active") {
                print ip, active[ip]
            }
        }
    }' "$lease_file"
}

# Count reservations from dhcpd.conf
count_dhcpd_reservations() {
    local conf="$DHCPD_CONF"
    [ -f "$conf" ] || return
    grep -c "fixed-address" "$conf" 2>/dev/null || true
}

# Parse DORA stats from syslog
parse_dhcpd_dora() {
    local logfile="/var/log/syslog"
    [ -f "$logfile" ] || logfile="/var/log/messages"
    [ -f "$logfile" ] || return

    local discovers offers requests acks naks declines releases
    discovers=$(grep -c "DHCPDISCOVER" "$logfile" 2>/dev/null || true)
    offers=$(grep -c "DHCPOFFER" "$logfile" 2>/dev/null || true)
    requests=$(grep -c "DHCPREQUEST" "$logfile" 2>/dev/null || true)
    acks=$(grep -c "DHCPACK" "$logfile" 2>/dev/null || true)
    naks=$(grep -c "DHCPNAK" "$logfile" 2>/dev/null || true)
    declines=$(grep -c "DHCPDECLINE" "$logfile" 2>/dev/null || true)
    releases=$(grep -c "DHCPRELEASE" "$logfile" 2>/dev/null || true)

    echo "${discovers}|${offers}|${requests}|${acks}|${naks}|${declines}|${releases}"
}

# ============================================================================
# KEA FUNCTIONS
# ============================================================================

kea_api_call() {
    local command="$1"
    curl -s --max-time 5 -X POST "${KEA_API}" \
        -H "Content-Type: application/json" \
        -d "{\"command\": \"${command}\", \"service\": [\"dhcp4\"]}" 2>/dev/null
}

parse_kea_leases_file() {
    local lease_file="$KEA_LEASES"
    [ -f "$lease_file" ] || return

    local now
    now=$(date +%s)

    awk -F',' -v now="$now" '
    NR > 1 && NF >= 9 {
        ip = $1
        expire = $7
        state = $9
        if (state == 0 && expire > now) {
            remaining = expire - now
            print ip, remaining
        }
    }' "$lease_file"
}

parse_kea_api_subnets() {
    local response
    response=$(kea_api_call "subnet4-list")
    if [ -z "$response" ]; then
        return 1
    fi

    echo "$response" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data[0]['result'] == 0:
    for s in data[0].get('arguments', {}).get('subnets', []):
        sid = s.get('id', 0)
        subnet = s.get('subnet', '')
        print(f'{sid}|{subnet}')
" 2>/dev/null
}

parse_kea_api_stats() {
    local response
    response=$(kea_api_call "statistic-get-all")
    if [ -z "$response" ]; then
        return 1
    fi
    echo "$response"
}

# ============================================================================
# METRIC COLLECTION
# ============================================================================

collect_metrics() {
    local start_time
    start_time=$(date +%s%N)
    local metrics=""

    # Exporter status
    metrics+="$(write_metric_header "dhcp_up" "gauge" "Exporter status (1=up, 0=down)")"$'\n'
    metrics+="$(write_metric_header "dhcp_exporter_info" "gauge" "Exporter version and backend")"$'\n'

    if [ "$DETECTED_BACKEND" = "unknown" ]; then
        metrics+="dhcp_up 0"$'\n'
        echo "$metrics"
        return
    fi

    metrics+="dhcp_up 1"$'\n'
    metrics+="dhcp_exporter_info{version=\"${VERSION}\",backend=\"${DETECTED_BACKEND}\"} 1"$'\n'

    local subnet_count=0
    local total_active=0

    if [ "$DETECTED_BACKEND" = "dhcpd" ]; then
        collect_dhcpd_metrics
    elif [ "$DETECTED_BACKEND" = "kea" ]; then
        collect_kea_metrics
    fi

    # Subnet count
    metrics+="$(write_metric_header "dhcp_subnets_total" "gauge" "Total number of configured subnets")"$'\n'
    metrics+="dhcp_subnets_total ${subnet_count}"$'\n'

    # Total active leases
    metrics+="$(write_metric_header "dhcp_leases_active_total" "gauge" "Total active leases across all subnets")"$'\n'
    metrics+="dhcp_leases_active_total ${total_active}"$'\n'

    # Lease file info
    if [ "$DETECTED_BACKEND" = "dhcpd" ] && [ -f "$DHCPD_LEASES" ]; then
        local file_age file_size
        file_age=$(( $(date +%s) - $(stat -c %Y "$DHCPD_LEASES") ))
        file_size=$(stat -c %s "$DHCPD_LEASES")
        metrics+="$(write_metric_header "dhcp_lease_file_age_seconds" "gauge" "Seconds since the lease file was last modified")"$'\n'
        metrics+="dhcp_lease_file_age_seconds ${file_age}"$'\n'
        metrics+="$(write_metric_header "dhcp_lease_file_size_bytes" "gauge" "Size of the lease file")"$'\n'
        metrics+="dhcp_lease_file_size_bytes ${file_size}"$'\n'
    elif [ "$DETECTED_BACKEND" = "kea" ] && [ -f "$KEA_LEASES" ]; then
        local file_age file_size
        file_age=$(( $(date +%s) - $(stat -c %Y "$KEA_LEASES") ))
        file_size=$(stat -c %s "$KEA_LEASES")
        metrics+="$(write_metric_header "dhcp_lease_file_age_seconds" "gauge" "Seconds since the lease file was last modified")"$'\n'
        metrics+="dhcp_lease_file_age_seconds ${file_age}"$'\n'
        metrics+="$(write_metric_header "dhcp_lease_file_size_bytes" "gauge" "Size of the lease file")"$'\n'
        metrics+="dhcp_lease_file_size_bytes ${file_size}"$'\n'
    fi

    # Execution time
    local end_time duration
    end_time=$(date +%s%N)
    duration=$(echo "scale=2; ($end_time - $start_time) / 1000000000" | bc 2>/dev/null || echo "0")
    metrics+="$(write_metric_header "dhcp_exporter_duration_seconds" "gauge" "Script execution time")"$'\n'
    metrics+="dhcp_exporter_duration_seconds ${duration}"$'\n'
    metrics+="$(write_metric_header "dhcp_exporter_last_run_timestamp" "gauge" "Unix timestamp of last successful run")"$'\n'
    metrics+="dhcp_exporter_last_run_timestamp $(date +%s)"$'\n'

    echo "$metrics"
}

collect_dhcpd_metrics() {
    # Parse subnets from config
    local subnet_data
    subnet_data=$(parse_dhcpd_subnets)

    # Get active leases
    local lease_data
    lease_data=$(count_dhcpd_leases)

    metrics+="$(write_metric_header "dhcp_subnet_pool_total" "gauge" "Total addresses in the pool")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_active" "gauge" "Currently leased addresses")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_free" "gauge" "Available addresses in the pool")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_utilization" "gauge" "Pool utilization percentage (0-100)")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_reserved" "gauge" "Number of static reservations")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_leases_expiring" "gauge" "Leases expiring within threshold")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_lease_longest_seconds" "gauge" "Remaining time on the longest lease")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_lease_shortest_seconds" "gauge" "Remaining time on the shortest lease")"$'\n'

    while IFS='|' read -r subnet name pool_total range_start range_end; do
        [ -z "$subnet" ] && continue
        subnet_count=$((subnet_count + 1))

        # Count active leases in this subnet range
        local active=0 longest=0 shortest=999999999
        local expiring_1h=0 expiring_4h=0 expiring_24h=0
        local start_int end_int
        start_int=$(ip_to_int "$range_start")
        end_int=$(ip_to_int "$range_end")

        while read -r lease_ip remaining; do
            [ -z "$lease_ip" ] && continue
            local lip
            lip=$(ip_to_int "$lease_ip")
            if [ "$lip" -ge "$start_int" ] && [ "$lip" -le "$end_int" ]; then
                active=$((active + 1))
                total_active=$((total_active + 1))
                [ "$remaining" -gt "$longest" ] && longest=$remaining
                [ "$remaining" -lt "$shortest" ] && shortest=$remaining
                [ "$remaining" -le 3600 ] && expiring_1h=$((expiring_1h + 1))
                [ "$remaining" -le 14400 ] && expiring_4h=$((expiring_4h + 1))
                [ "$remaining" -le 86400 ] && expiring_24h=$((expiring_24h + 1))
            fi
        done <<< "$lease_data"

        local free=$((pool_total - active))
        [ $free -lt 0 ] && free=0
        local util=0
        if [ "$pool_total" -gt 0 ]; then
            util=$(echo "scale=2; $active * 100 / $pool_total" | bc 2>/dev/null || echo "0")
        fi
        [ $active -eq 0 ] && shortest=0

        local reserved
        reserved=$(count_dhcpd_reservations)

        metrics+="dhcp_subnet_pool_total{subnet=\"${subnet}\",name=\"${name}\"} ${pool_total}"$'\n'
        metrics+="dhcp_subnet_pool_active{subnet=\"${subnet}\",name=\"${name}\"} ${active}"$'\n'
        metrics+="dhcp_subnet_pool_free{subnet=\"${subnet}\",name=\"${name}\"} ${free}"$'\n'
        metrics+="dhcp_subnet_pool_utilization{subnet=\"${subnet}\",name=\"${name}\"} ${util}"$'\n'
        metrics+="dhcp_subnet_pool_reserved{subnet=\"${subnet}\",name=\"${name}\"} ${reserved}"$'\n'
        metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"1h\"} ${expiring_1h}"$'\n'
        metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"4h\"} ${expiring_4h}"$'\n'
        metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"24h\"} ${expiring_24h}"$'\n'
        metrics+="dhcp_subnet_lease_longest_seconds{subnet=\"${subnet}\",name=\"${name}\"} ${longest}"$'\n'
        metrics+="dhcp_subnet_lease_shortest_seconds{subnet=\"${subnet}\",name=\"${name}\"} ${shortest}"$'\n'
    done <<< "$subnet_data"

    # DORA stats
    local dora
    dora=$(parse_dhcpd_dora)
    if [ -n "$dora" ]; then
        IFS='|' read -r discovers offers requests acks naks declines releases <<< "$dora"
        metrics+="$(write_metric_header "dhcp_discovers_total" "counter" "Total DHCPDISCOVER packets received")"$'\n'
        metrics+="dhcp_discovers_total ${discovers}"$'\n'
        metrics+="$(write_metric_header "dhcp_offers_total" "counter" "Total DHCPOFFER packets sent")"$'\n'
        metrics+="dhcp_offers_total ${offers}"$'\n'
        metrics+="$(write_metric_header "dhcp_requests_total" "counter" "Total DHCPREQUEST packets received")"$'\n'
        metrics+="dhcp_requests_total ${requests}"$'\n'
        metrics+="$(write_metric_header "dhcp_acks_total" "counter" "Total DHCPACK packets sent")"$'\n'
        metrics+="dhcp_acks_total ${acks}"$'\n'
        metrics+="$(write_metric_header "dhcp_naks_total" "counter" "Total DHCPNAK packets sent")"$'\n'
        metrics+="dhcp_naks_total ${naks}"$'\n'
        metrics+="$(write_metric_header "dhcp_declines_total" "counter" "Total DHCPDECLINE packets received")"$'\n'
        metrics+="dhcp_declines_total ${declines}"$'\n'
        metrics+="$(write_metric_header "dhcp_releases_total" "counter" "Total DHCPRELEASE packets received")"$'\n'
        metrics+="dhcp_releases_total ${releases}"$'\n'
    fi
}

collect_kea_metrics() {
    metrics+="$(write_metric_header "dhcp_subnet_pool_total" "gauge" "Total addresses in the pool")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_active" "gauge" "Currently leased addresses")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_free" "gauge" "Available addresses in the pool")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_utilization" "gauge" "Pool utilization percentage (0-100)")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_pool_reserved" "gauge" "Number of static reservations")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_leases_expiring" "gauge" "Leases expiring within threshold")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_lease_longest_seconds" "gauge" "Remaining time on the longest lease")"$'\n'
    metrics+="$(write_metric_header "dhcp_subnet_lease_shortest_seconds" "gauge" "Remaining time on the shortest lease")"$'\n'

    if [ "$KEA_USE_API" = "true" ]; then
        collect_kea_api_metrics
    else
        collect_kea_file_metrics
    fi
}

collect_kea_api_metrics() {
    local stats_json
    stats_json=$(kea_api_call "statistic-get-all")

    if [ -z "$stats_json" ]; then
        log_warn "Kea API not responding, falling back to file mode"
        collect_kea_file_metrics
        return
    fi

    # Parse stats via python3
    echo "$stats_json" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data[0]['result'] == 0:
    args = data[0].get('arguments', {})
    for key, val in args.items():
        if val and isinstance(val, list):
            v = val[0][0] if isinstance(val[0], list) else val[0]
            print(f'{key}={v}')
" 2>/dev/null | while IFS='=' read -r key value; do
        case "$key" in
            subnet*total-addresses*)
                local sid="${key#subnet[}"
                sid="${sid%%]*}"
                metrics+="dhcp_subnet_pool_total{subnet=\"${sid}\"} ${value}"$'\n'
                ;;
            subnet*assigned-addresses*)
                local sid="${key#subnet[}"
                sid="${sid%%]*}"
                metrics+="dhcp_subnet_pool_active{subnet=\"${sid}\"} ${value}"$'\n'
                ;;
            pkt4-discover-received)
                metrics+="$(write_metric_header "dhcp_discovers_total" "counter" "Total DHCPDISCOVER packets received")"$'\n'
                metrics+="dhcp_discovers_total ${value}"$'\n'
                ;;
            pkt4-offer-sent)
                metrics+="$(write_metric_header "dhcp_offers_total" "counter" "Total DHCPOFFER packets sent")"$'\n'
                metrics+="dhcp_offers_total ${value}"$'\n'
                ;;
            pkt4-request-received)
                metrics+="$(write_metric_header "dhcp_requests_total" "counter" "Total DHCPREQUEST packets received")"$'\n'
                metrics+="dhcp_requests_total ${value}"$'\n'
                ;;
            pkt4-ack-sent)
                metrics+="$(write_metric_header "dhcp_acks_total" "counter" "Total DHCPACK packets sent")"$'\n'
                metrics+="dhcp_acks_total ${value}"$'\n'
                ;;
            pkt4-nak-sent)
                metrics+="$(write_metric_header "dhcp_naks_total" "counter" "Total DHCPNAK packets sent")"$'\n'
                metrics+="dhcp_naks_total ${value}"$'\n'
                ;;
            pkt4-decline-received)
                metrics+="$(write_metric_header "dhcp_declines_total" "counter" "Total DHCPDECLINE packets received")"$'\n'
                metrics+="dhcp_declines_total ${value}"$'\n'
                ;;
            pkt4-release-received)
                metrics+="$(write_metric_header "dhcp_releases_total" "counter" "Total DHCPRELEASE packets received")"$'\n'
                metrics+="dhcp_releases_total ${value}"$'\n'
                ;;
        esac
    done
}

collect_kea_file_metrics() {
    local lease_data
    lease_data=$(parse_kea_leases_file)
    local now
    now=$(date +%s)

    # Simple lease counting from CSV
    while read -r lease_ip remaining; do
        [ -z "$lease_ip" ] && continue
        total_active=$((total_active + 1))
    done <<< "$lease_data"
}

# ============================================================================
# OUTPUT
# ============================================================================

output_metrics() {
    local all_metrics
    all_metrics=$(collect_metrics)

    case "$MODE" in
        stdout)
            echo "$all_metrics"
            ;;
        textfile)
            mkdir -p "$TEXTFILE_DIR"
            local tmp_file
            tmp_file=$(mktemp "${TEXTFILE_DIR}/.dhcp-metrics.XXXXXX")
            echo "$all_metrics" > "$tmp_file"
            mv "$tmp_file" "${TEXTFILE_DIR}/dhcp-metrics.prom"
            log_info "Wrote metrics to ${TEXTFILE_DIR}/dhcp-metrics.prom"
            ;;
        http)
            run_http_server "$all_metrics"
            ;;
    esac
}

run_http_server() {
    log_info "Starting HTTP server on port ${HTTP_PORT}"
    while true; do
        local all_metrics
        all_metrics=$(collect_metrics)

        {
            echo -e "HTTP/1.1 200 OK\r"
            echo -e "Content-Type: text/plain; version=0.0.4; charset=utf-8\r"
            echo -e "Content-Length: ${#all_metrics}\r"
            echo -e "\r"
            echo "$all_metrics"
        } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null || \
        {
            echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n${all_metrics}"
        } | nc -l "$HTTP_PORT" 2>/dev/null

        if $ONCE; then
            break
        fi
    done
}

# ============================================================================
# MAIN
# ============================================================================

main() {
    parse_args "$@"
    acquire_lock
    detect_backend
    log_info "Detected DHCP backend: ${DETECTED_BACKEND}"
    output_metrics
}

main "$@"
