#!/bin/bash
# ==========================================================
#  GESTOR DE RED Y DHCP (KEA)  -  MENU UNIFICADO
#  Practica de Servicios de Red
#
#  Un solo fichero que incluye (incrustados) los tres scripts:
#    - conf-red-ubuntu-ensX.sh   (Ubuntu / Netplan)
#    - conf-red-debian-ensX.sh   (Debian / ifupdown)
#    - kea-dhcp-manager.sh       (servidor DHCP Kea)
#
#  Uso:
#    sudo ./gestor-red.sh              menu interactivo
#    sudo ./gestor-red.sh red          configurar la red (autodetecta SO)
#    sudo ./gestor-red.sh kea          gestor de Kea
#    sudo ./gestor-red.sh completo     asistente red -> Kea
#         ./gestor-red.sh estado       estado de red y Kea
#         ./gestor-red.sh --extraer DIR  guarda los 3 scripts originales en DIR
#         ./gestor-red.sh --help
#
#  Orden recomendado: 1) red  ->  2) Kea.
#  La tarjeta donde da servicio Kea debe tener IP ESTATICA.
# ==========================================================

VERSION="1.0"
KEA_CONF="${KEA_CONF:-/etc/kea/kea-dhcp4.conf}"
KEA_MGR_DIR_DEFAULT="${KEA_MGR_DIR:-/etc/kea/kea-manager}"
WORKDIR=""
SCRIPT_PATH=""
OS_NAME="desconocido"
NET_KIND="desconocido"      # ubuntu | debian | desconocido

cleanup() { [ -n "$WORKDIR" ] && rm -rf "$WORKDIR"; }
trap cleanup EXIT
# Ctrl+C dentro de un script hijo lo corta a el, pero no cierra este menu
trap 'echo' INT

# ---------------------------------------------------------
# Utilidades
# ---------------------------------------------------------

ask_yn() {
    local prompt=$1 ans
    while true; do
        read -rp "$prompt (s/n): " ans || exit 0
        case "$ans" in
            [sS]) return 0 ;;
            [nN]) return 1 ;;
            *) echo "Responde s o n." ;;
        esac
    done
}

pause() { read -rp "Pulsa ENTER para volver al menu..." _ || exit 0; }

# ---------------------------------------------------------
# Deteccion del sistema
# ---------------------------------------------------------

detect_backend() {
    local id="" idlike="" pretty=""
    if [ -r /etc/os-release ]; then
        IFS='|' read -r id idlike pretty < <( . /etc/os-release; echo "${ID:-}|${ID_LIKE:-}|${PRETTY_NAME:-}" )
    fi
    [ -n "$pretty" ] && OS_NAME=$pretty

    case "$id" in
        ubuntu)          NET_KIND=ubuntu ;;
        debian|raspbian) NET_KIND=debian ;;
        *)
            case " $idlike " in
                *" ubuntu "*) NET_KIND=ubuntu ;;
                *" debian "*) NET_KIND=debian ;;
            esac
            ;;
    esac

    if [ "$NET_KIND" = "desconocido" ]; then
        if   command -v netplan >/dev/null 2>&1; then NET_KIND=ubuntu
        elif command -v ifup    >/dev/null 2>&1; then NET_KIND=debian
        fi
    fi
}

net_label() {
    case "$NET_KIND" in
        ubuntu) echo "Ubuntu / Netplan" ;;
        debian) echo "Debian / ifupdown" ;;
        *)      echo "no detectado" ;;
    esac
}

# ---------------------------------------------------------
# Extraccion y ejecucion de los scripts incrustados
# ---------------------------------------------------------

ensure_workdir() {
    if [ -z "$WORKDIR" ] || [ ! -d "$WORKDIR" ]; then
        WORKDIR=$(mktemp -d /tmp/gestor-red.XXXXXX) || { echo "ERROR: no puedo crear directorio temporal"; exit 1; }
        chmod 700 "$WORKDIR"
    fi
}

# extract_script ubuntu|debian|kea [directorio]  ->  deja la ruta en SCRIPT_PATH
# (variable global a proposito: una $(...) perderia WORKDIR)
extract_script() {
    local name=$1 dir=${2:-} file
    if [ -z "$dir" ]; then
        ensure_workdir
        dir=$WORKDIR
    fi
    case "$name" in
        ubuntu) file="$dir/conf-red-ubuntu-ensX.sh";  emit_red_ubuntu   > "$file" ;;
        debian) file="$dir/conf-red-debian-ensX.sh";  emit_red_debian   > "$file" ;;
        kea)    file="$dir/kea-dhcp-manager.sh";      emit_kea_manager  > "$file" ;;
        *) return 1 ;;
    esac
    chmod 755 "$file"
    SCRIPT_PATH=$file
}

run_script() {
    local name=$1 rc; shift
    extract_script "$name" || return 1
    echo ""
    bash "$SCRIPT_PATH" "$@"
    rc=$?
    echo ""
    if [ "$rc" -eq 0 ]; then
        echo ">>> Script '$name' finalizado."
    else
        echo ">>> Script '$name' termino con codigo $rc."
    fi
    return "$rc"
}

extract_all() {
    local dir=${1:-.}
    mkdir -p "$dir" || { echo "ERROR: no puedo crear $dir"; return 1; }
    extract_script ubuntu "$dir"; echo "Creado: $SCRIPT_PATH"
    extract_script debian "$dir"; echo "Creado: $SCRIPT_PATH"
    extract_script kea    "$dir"; echo "Creado: $SCRIPT_PATH"
}

# ---------------------------------------------------------
# Acciones del menu
# ---------------------------------------------------------

choose_network_manual() {
    local c
    echo ""
    echo "  1) Ubuntu / Netplan"
    echo "  2) Debian / ifupdown"
    echo "  0) Volver"
    read -rp "> " c || exit 0
    case "$c" in
        1) run_script ubuntu ;;
        2) run_script debian ;;
        *) return 1 ;;
    esac
}

run_network() {
    case "$NET_KIND" in
        ubuntu) run_script ubuntu ;;
        debian) run_script debian ;;
        *)
            echo "No he podido detectar si el sistema usa Netplan o ifupdown."
            echo "Elige el script manualmente:"
            choose_network_manual
            ;;
    esac
}

wizard() {
    echo ""
    echo "=== ASISTENTE COMPLETO ==="
    echo "Paso 1/2: configuracion de red ($(net_label))"
    echo "(la tarjeta donde dara servicio Kea debe quedar con IP estatica)"
    echo ""
    if ask_yn "Configurar la red ahora?"; then
        run_network
    fi
    echo ""
    echo "Paso 2/2: gestor de Kea DHCP"
    echo "(si Kea no esta instalado, usa la opcion 9 del gestor: instalar desde .deb)"
    echo ""
    if ask_yn "Abrir el gestor de Kea ahora?"; then
        run_script kea
    fi
}

kea_service_name() {
    local s
    for s in kea-dhcp4-server kea-dhcp4; do
        if systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "^$s.service"; then
            echo "$s"; return 0
        fi
    done
    return 1
}

show_status() {
    local svc ref ifc ip n=0 f
    echo ""
    echo "=== SISTEMA ==="
    echo "$OS_NAME   ->   $(net_label)"
    echo ""
    echo "=== INTERFACES ==="
    ip -br addr
    echo ""
    echo "=== RUTAS ==="
    ip route
    echo ""
    echo "=== SERVICIOS ==="
    for svc in NetworkManager systemd-networkd networking kea-dhcp4-server kea-dhcp4; do
        if systemctl list-unit-files "$svc.service" 2>/dev/null | grep -q "^$svc.service"; then
            printf "%-22s %s\n" "$svc" "$(systemctl is-active "$svc" 2>/dev/null)"
        fi
    done
    echo ""
    echo "=== KEA: interfaces/IP configuradas vs red real ==="
    if [ -f "$KEA_CONF" ]; then
        while read -r ref; do
            [ -z "$ref" ] && continue
            ifc=${ref%%/*}; ip=${ref#*/}
            if ip -o -4 addr show dev "$ifc" 2>/dev/null | grep -qE "inet ${ip//./\\.}/"; then
                echo "  OK     $ifc tiene $ip"
            else
                echo "  FALTA  $ifc NO tiene $ip"
            fi
            n=$((n+1))
        done < <(grep -oE '"[A-Za-z0-9._-]+/[0-9]{1,3}(\.[0-9]{1,3}){3}"' "$KEA_CONF" | tr -d '"' | sort -u)
        [ "$n" -eq 0 ] && echo "  (Kea usa interfaces '*' o no hay referencias explicitas)"
    else
        echo "  No existe $KEA_CONF (Kea sin configurar todavia)"
    fi
    n=0
    for f in "$KEA_MGR_DIR_DEFAULT"/net_*.conf; do [ -e "$f" ] && n=$((n+1)); done
    echo "  Redes definidas en el gestor de Kea: $n"
}

usage() {
    sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
}

main_menu() {
    local c
    while true; do
        echo ""
        echo "=============================================="
        echo "   GESTOR DE RED Y DHCP (KEA)   v$VERSION"
        echo "=============================================="
        echo " Sistema: $OS_NAME"
        echo ""
        echo "  1) Configurar la red de este servidor   [$(net_label)]"
        echo "  2) Gestor de Kea DHCP (redes, rangos, reservas...)"
        echo "  3) Asistente completo: red -> Kea DHCP"
        echo "  4) Estado actual (red, servicios y Kea)"
        echo "  5) Avanzado: elegir el script de red manualmente"
        echo "  0) Salir"
        echo ""
        read -rp "> " c || exit 0
        case "$c" in
            1) run_network;            pause ;;
            2) run_script kea;         pause ;;
            3) wizard;                 pause ;;
            4) show_status;            pause ;;
            5) choose_network_manual;  pause ;;
            0) echo "Hasta luego."; exit 0 ;;
            *) echo "Opcion no valida." ;;
        esac
    done
}

# ---------------------------------------------------------
# Scripts incrustados (contenido original, sin modificar)
# ---------------------------------------------------------

emit_red_ubuntu() {
cat <<'__EOF_RED_UBUNTU__'
#!/bin/bash

# ==========================================================
# CONFIGURACION DE RED - UBUNTU / NETPLAN
# Practica de Servicios de Red  -  VERSION CORREGIDA v3 (compatible con kea-dhcp-manager.sh)
# ==========================================================

NETPLAN_DIR="/etc/netplan"
# Prefijo 99: se procesa el ULTIMO, asi que gana sobre otros ficheros
NETPLAN_FILE="$NETPLAN_DIR/99-redubuntu.yaml"
TEMP_FILE="$(mktemp /tmp/netplan.XXXXXX)"
BACKUP_DIR=""
KEA_CONF="${KEA_CONF:-/etc/kea/kea-dhcp4.conf}"   # mismo fichero que usa kea-dhcp-manager.sh

trap 'rm -f "$TEMP_FILE"' EXIT

# ---------------------------------------------------------
# Funciones auxiliares
# ---------------------------------------------------------

valid_ip() {
    local ip=$1 IFS=. o x
    [[ $ip =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 1
    read -ra o <<< "$ip"
    for x in "${o[@]}"; do
        (( 10#$x <= 255 )) || return 1
    done
    return 0
}

ask_yes_no() {
    local prompt=$1 ans
    while true; do
        read -rp "$prompt (s/n): " ans || exit 1
        case "$ans" in
            [sS]) return 0 ;;
            [nN]) return 1 ;;
            *) echo "Responde s o n." ;;
        esac
    done
}

ask_ip() {
    local prompt=$1 val
    while true; do
        read -rp "$prompt: " val || exit 1
        if valid_ip "$val"; then
            REPLY_VALUE=$(norm_ip "$val")
            return 0
        fi
        echo "IP no valida, intentalo de nuevo."
    done
}

ask_prefix() {
    local val
    while true; do
        read -rp "Introduce prefijo (ej: 24): " val || exit 1
        if [[ "$val" =~ ^[0-9]{1,2}$ ]] && (( 10#$val >= 1 && 10#$val <= 32 )); then
            REPLY_VALUE=$((10#$val))
            return 0
        fi
        echo "Prefijo no valido (1-32)."
    done
}

ip2int() {
    local IFS=. a b c d
    read -r a b c d <<< "$1"
    echo $(( (10#$a << 24) | (10#$b << 16) | (10#$c << 8) | 10#$d ))
}

int2ip() {
    local n=$1
    echo "$(( (n >> 24) & 255 )).$(( (n >> 16) & 255 )).$(( (n >> 8) & 255 )).$(( n & 255 ))"
}

# Normaliza una IP a decimal (quita ceros a la izquierda: 192.168.001.010 -> 192.168.1.10)
norm_ip() { int2ip "$(ip2int "$1")"; }

# net_addr IP PREFIJO  ->  direccion de red (ej: 192.168.1.0)
net_addr() {
    local ipn mask p=$2
    ipn=$(ip2int "$1")
    mask=$(( (0xFFFFFFFF << (32 - p)) & 0xFFFFFFFF ))
    int2ip $(( ipn & mask ))
}

# Pide la gateway y comprueba que pertenece a la subred de la interfaz
ask_gateway() {
    local ip_addr=$1 prefix=$2 gw
    while true; do
        ask_ip "Introduce la gateway"
        gw=$REPLY_VALUE
        if [ "$(net_addr "$gw" "$prefix")" = "$(net_addr "$ip_addr" "$prefix")" ]; then
            return 0
        fi
        echo "AVISO: la gateway $gw NO esta en la red $(net_addr "$ip_addr" "$prefix")/$prefix."
        if ask_yes_no "Usarla igualmente?"; then
            return 0
        fi
    done
}

# Deshace los cambios: quita nuestro fichero y devuelve los originales
restore_all() {
    echo ""
    echo "Restaurando configuracion anterior..."
    rm -f "$NETPLAN_FILE"
    if [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ]; then
        mv "$BACKUP_DIR"/*.yaml "$NETPLAN_DIR"/ 2>/dev/null
        rmdir "$BACKUP_DIR" 2>/dev/null
    fi
    netplan apply
    echo "Configuracion anterior restaurada."
}

# --- Integracion con kea-dhcp-manager.sh ---------------------------------
# Kea escucha en "interfaz/IP" concretas. Devuelve esas referencias leidas de
# su fichero de configuracion (una por linea, formato ens33/150.160.229.200).
kea_refs() {
    [ -f "$KEA_CONF" ] || return 0
    grep -oE '"[A-Za-z0-9._-]+/[0-9]{1,3}(\.[0-9]{1,3}){3}"' "$KEA_CONF" | tr -d '"' | sort -u
}

kea_service_name() {
    local s
    for s in kea-dhcp4-server kea-dhcp4; do
        if systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "^$s.service"; then
            echo "$s"; return 0
        fi
    done
    return 1
}

# Tras cambiar la red: comprueba que Kea sigue teniendo sus interfaces/IP y lo reinicia
kea_post_check() {
    local svc ref ifc ip missing=false
    svc=$(kea_service_name) || svc=""
    [ -z "$svc" ] && [ ! -f "$KEA_CONF" ] && return 0

    echo ""
    echo "=== COMPROBACION CON KEA DHCP ==="
    mapfile -t kea_ref_list < <(kea_refs)
    for ref in "${kea_ref_list[@]}"; do
        [ -z "$ref" ] && continue
        ifc=${ref%%/*}; ip=${ref#*/}
        if ip -o -4 addr show dev "$ifc" 2>/dev/null | grep -qE "inet ${ip//./\\.}/"; then
            echo "  OK     $ifc tiene $ip"
        else
            echo "  FALTA  $ifc NO tiene $ip (Kea esta configurado para usarla)"
            missing=true
        fi
    done

    if [ "$missing" = true ]; then
        echo ""
        echo "AVISO: Kea apunta a interfaces/IP que ya no existen. No se reinicia."
        echo "       Ajusta Kea con: kea-dhcp-manager.sh -> opcion 3 -> 8 (Interfaz / IP)"
        echo "       y despues opcion 6 (APLICAR)."
        return 0
    fi

    if [ -n "$svc" ] && systemctl is-active --quiet "$svc"; then
        echo "Reiniciando $svc para que reabra sus sockets con las IP actuales..."
        if timeout 30 systemctl restart "$svc"; then
            echo "  $svc reiniciado correctamente."
        else
            echo "  AVISO: $svc no arranco. Revisa: journalctl -u $svc --no-pager -n 30"
        fi
    fi
}

# ---------------------------------------------------------
# 1. Comprobaciones iniciales
# ---------------------------------------------------------

echo "=========================================="
echo "   CONFIGURACION DE RED - UBUNTU"
echo "=========================================="
echo ""

if [ "$EUID" -ne 0 ]; then
    echo "ERROR: Este script debe ejecutarse como root."
    echo "Ejecutalo con: sudo $0"
    exit 1
fi

if ! command -v netplan >/dev/null 2>&1; then
    echo "ERROR: netplan no esta instalado (apt install netplan.io)."
    exit 1
fi

# ---------------------------------------------------------
# 2. Gestores de red y renderer
# ---------------------------------------------------------

echo "=== GESTORES DE RED ==="
for svc in NetworkManager systemd-networkd; do
    printf "%-20s %s\n" "$svc" "$(systemctl is-active "$svc" 2>/dev/null)"
done
echo ""

echo "Se utilizara Netplan (renderer por defecto del sistema)."
echo ""

if [ -n "$SSH_CONNECTION" ]; then
    echo "AVISO: Estas conectado por SSH. Si cambias la interfaz por la que"
    echo "       entras podrias perder la conexion (se usara 'netplan try'"
    echo "       para revertir automaticamente si no confirmas)."
    echo ""
fi

# ---------------------------------------------------------
# 3. Deteccion de interfaces
# ---------------------------------------------------------

echo "=== INTERFACES DETECTADAS ==="
echo ""

interfaces=()

for iface_path in /sys/class/net/*; do
    iface=$(basename "$iface_path")
    [ "$iface" = "lo" ] && continue
    [ -e "$iface_path/device" ] || continue     # solo interfaces fisicas
    [ -d "$iface_path/wireless" ] && continue   # sin wifi (no son "ethernets")
    interfaces+=("$iface")
    echo " - $iface"
done

echo ""

if [ "${#interfaces[@]}" -eq 0 ]; then
    echo "ERROR: No se han encontrado interfaces de red fisicas."
    exit 1
fi

# ---------------------------------------------------------
# 4. Seleccion de interfaces
# ---------------------------------------------------------

while true; do
    read -rp "Cuantas interfaces quieres configurar?: " num_ifaces || exit 1
    if [[ "$num_ifaces" =~ ^[0-9]+$ ]] && [ "$num_ifaces" -ge 1 ] \
       && [ "$num_ifaces" -le "${#interfaces[@]}" ]; then
        break
    fi
    echo "Introduce un numero entre 1 y ${#interfaces[@]}."
done

used_interfaces=()
echo ""

for ((i=0; i<num_ifaces; i++)); do
    while true; do
        echo "Interfaces disponibles:"
        for ((j=0; j<${#interfaces[@]}; j++)); do
            echo "$((j+1))) ${interfaces[$j]}"
        done
        echo ""

        read -rp "Selecciona el numero de la interfaz $((i+1)): " selection || exit 1

        if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] \
           || [ "$selection" -gt "${#interfaces[@]}" ]; then
            echo "Seleccion no valida."
            echo ""
            continue
        fi

        iface="${interfaces[$((selection-1))]}"

        already_used=false
        for used in "${used_interfaces[@]}"; do
            [ "$used" = "$iface" ] && already_used=true
        done

        if [ "$already_used" = true ]; then
            echo "Esa interfaz ya ha sido seleccionada."
            echo ""
            continue
        fi

        used_interfaces+=("$iface")
        echo "Interfaz seleccionada: $iface"
        echo ""
        break
    done
done

# ---------------------------------------------------------
# 4b. Interfaces no seleccionadas (se preguntan ANTES de generar el YAML
#     para dejarlas tambien declaradas como apagadas de forma persistente)
# ---------------------------------------------------------

unused=()
for iface in "${interfaces[@]}"; do
    in_use=false
    for used in "${used_interfaces[@]}"; do
        [ "$iface" = "$used" ] && in_use=true
    done
    [ "$in_use" = false ] && unused+=("$iface")
done

clean_unused=false
if [ "${#unused[@]}" -gt 0 ]; then
    echo "Interfaces NO seleccionadas: ${unused[*]}"
    if ask_yes_no "Quieres bajarlas y limpiarlas (down + flush)?"; then
        clean_unused=true
        # Aviso si Kea DHCP escucha en alguna de ellas
        mapfile -t kea_ref_list < <(kea_refs)
        for ref in "${kea_ref_list[@]}"; do
            [ -z "$ref" ] && continue
            for iface in "${unused[@]}"; do
                if [ "${ref%%/*}" = "$iface" ]; then
                    echo "AVISO: Kea DHCP esta configurado para escuchar en $iface (${ref#*/})"
                    echo "       y vas a bajarla: Kea dejaria de dar servicio en esa red."
                    ask_yes_no "Continuar igualmente?" || { echo "Operacion cancelada."; exit 0; }
                fi
            done
        done
    fi
    echo ""
fi

# ---------------------------------------------------------
# 5. Generacion del YAML (NO se toca el sistema todavia)
# ---------------------------------------------------------

cat <<EOF > "$TEMP_FILE"
network:
  version: 2
  ethernets:
EOF

idx=0

# Policy routing: solo tiene sentido con 2 o mas interfaces
policy_routing=false
if [ "${#used_interfaces[@]}" -gt 1 ]; then
    echo "Con varias redes, las respuestas de las tarjetas secundarias pueden"
    echo "salir por la gateway de otra tarjeta y fallar (enrutamiento asimetrico)."
    echo "Con 'policy routing' cada tarjeta responde siempre por su propia gateway."
    echo ""
    if ask_yes_no "Activar policy routing para las interfaces estaticas con gateway? (recomendado)"; then
        policy_routing=true
    fi
    echo ""
fi

for iface in "${used_interfaces[@]}"; do

    idx=$((idx + 1))
    metric=$((idx * 100))      # 100, 200, 300... (menor = preferida)
    table=$((100 + idx))       # tabla de rutas propia (101, 102...)

    echo "=========================================="
    echo "Configurando interfaz: $iface"
    echo "=========================================="

    echo "    $iface:" >> "$TEMP_FILE"

    # Las interfaces secundarias no deben retrasar el arranque
    if [ "$idx" -gt 1 ]; then
        echo "      optional: true" >> "$TEMP_FILE"
    fi

    if ask_yes_no "Quieres usar DHCP en $iface?"; then

        echo "      dhcp4: true" >> "$TEMP_FILE"
        echo "      dhcp6: false" >> "$TEMP_FILE"

        # Metrica propia para que su gateway no choque con las de otras tarjetas
        echo "      dhcp4-overrides:" >> "$TEMP_FILE"
        echo "        route-metric: $metric" >> "$TEMP_FILE"

        echo "Configuracion DHCP seleccionada."

    else

        ask_ip "Introduce la IP"
        ip_addr=$REPLY_VALUE
        ask_prefix
        prefix=$REPLY_VALUE

        echo "      dhcp4: false" >> "$TEMP_FILE"
        echo "      dhcp6: false" >> "$TEMP_FILE"
        echo "      addresses:" >> "$TEMP_FILE"
        echo "        - $ip_addr/$prefix" >> "$TEMP_FILE"

        if ask_yes_no "Quieres configurar DNS estatico?"; then
            while true; do
                read -rp "Introduce DNS separados por espacio: " dns || exit 1
                ok=true
                for d in $dns; do
                    valid_ip "$d" || ok=false
                done
                if [ "$ok" = true ] && [ -n "$dns" ]; then
                    break
                fi
                echo "Alguna IP de DNS no es valida."
            done
            # Normaliza IPs y evita "[, 8.8.8.8]" si el usuario escribe espacios de mas
            dns_list=""
            for d in $dns; do dns_list="${dns_list:+$dns_list, }$(norm_ip "$d")"; done
            echo "      nameservers:" >> "$TEMP_FILE"
            echo "        addresses: [$dns_list]" >> "$TEMP_FILE"
        fi

        # Cada interfaz puede tener SU gateway (con metrica distinta)
        if ask_yes_no "Tiene gateway?"; then
            ask_gateway "$ip_addr" "$prefix"
            gateway=$REPLY_VALUE
            network=$(net_addr "$ip_addr" "$prefix")

            echo "      routes:" >> "$TEMP_FILE"
            echo "        - to: 0.0.0.0/0" >> "$TEMP_FILE"
            echo "          via: $gateway" >> "$TEMP_FILE"
            echo "          metric: $metric" >> "$TEMP_FILE"

            if [ "$policy_routing" = true ]; then
                # Tabla propia: el trafico originado en esta IP sale por esta gateway
                echo "        - to: 0.0.0.0/0" >> "$TEMP_FILE"
                echo "          via: $gateway" >> "$TEMP_FILE"
                echo "          table: $table" >> "$TEMP_FILE"
                echo "        - to: $network/$prefix" >> "$TEMP_FILE"
                echo "          scope: link" >> "$TEMP_FILE"
                echo "          table: $table" >> "$TEMP_FILE"
                echo "      routing-policy:" >> "$TEMP_FILE"
                echo "        - from: $ip_addr/32" >> "$TEMP_FILE"
                echo "          table: $table" >> "$TEMP_FILE"
            fi
        fi
    fi

    echo ""
done

# Interfaces no seleccionadas: declaradas apagadas y sin IP (persistente)
if [ "$clean_unused" = true ]; then
    for iface in "${unused[@]}"; do
        {
            echo "    $iface:"
            echo "      dhcp4: false"
            echo "      dhcp6: false"
            echo "      link-local: []"
            echo "      optional: true"
            echo "      activation-mode: off"
        } >> "$TEMP_FILE"
    done
fi

# ---------------------------------------------------------
# 6. Preview y confirmacion
# ---------------------------------------------------------

echo "=========================================="
echo "       PREVIEW DE NETPLAN"
echo "=========================================="
cat "$TEMP_FILE"
echo "=========================================="
echo ""

if ! ask_yes_no "Quieres aplicar esta configuracion de forma permanente?"; then
    echo ""
    echo "Operacion cancelada. No se ha modificado nada."
    exit 0
fi

# ---------------------------------------------------------
# 7. Backup de ficheros existentes de Netplan
# ---------------------------------------------------------
# Netplan fusiona TODOS los .yaml de /etc/netplan. Si hay otros
# (p.ej. 50-cloud-init.yaml) pueden pisar o mezclarse con el nuestro.

BACKUP_DIR="$NETPLAN_DIR/backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR" || { echo "ERROR al crear $BACKUP_DIR"; exit 1; }

# Copia de seguridad de nuestro propio fichero si ya existia
[ -f "$NETPLAN_FILE" ] && cp -p "$NETPLAN_FILE" "$BACKUP_DIR/"

others=()
for f in "$NETPLAN_DIR"/*.yaml; do
    [ -e "$f" ] || continue
    [ "$f" = "$NETPLAN_FILE" ] && continue
    others+=("$f")
done

if [ "${#others[@]}" -gt 0 ]; then
    echo ""
    echo "Se han encontrado otros ficheros de Netplan que se fusionan con el tuyo:"
    for f in "${others[@]}"; do
        echo " - $f"
    done
    echo ""
    if ask_yes_no "Moverlos a $BACKUP_DIR para que no interfieran? (recomendado)"; then
        for f in "${others[@]}"; do
            mv "$f" "$BACKUP_DIR/" || echo "ERROR al mover $f"
        done
    else
        echo "AVISO: seguiran fusionandose con tu configuracion."
    fi
fi

# Evitar que cloud-init regenere su YAML al reiniciar
if [ -d /etc/cloud/cloud.cfg.d ] && [ ! -f /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg ]; then
    echo ""
    if ask_yes_no "Desactivar la configuracion de red de cloud-init? (evita que pise tu config al reiniciar)"; then
        echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
    fi
fi

# ---------------------------------------------------------
# 8. Instalar y aplicar
# ---------------------------------------------------------

if ! cp "$TEMP_FILE" "$NETPLAN_FILE"; then
    echo "ERROR al copiar la configuracion de Netplan."
    restore_all
    exit 1
fi

# Netplan exige permisos restrictivos (600)
chmod 600 "$NETPLAN_FILE"
chown root:root "$NETPLAN_FILE"

echo ""
echo "Validando configuracion (netplan generate)..."
if ! netplan generate; then
    if grep -q 'activation-mode' "$NETPLAN_FILE"; then
        echo "AVISO: esta version de Netplan no soporta 'activation-mode'; se quita y se reintenta."
        sed -i '/activation-mode: off/d' "$NETPLAN_FILE"
        if ! netplan generate; then
            echo "ERROR: el YAML generado no es valido."
            restore_all
            exit 1
        fi
    else
        echo "ERROR: el YAML generado no es valido."
        restore_all
        exit 1
    fi
fi

echo ""
echo "Aplicando con 'netplan try' (60s para confirmar; si no, se revierte)..."
echo "Pulsa ENTER cuando te lo pida para aceptar los cambios."
echo ""

if ! netplan try --timeout 60; then
    echo "ERROR: Netplan no pudo aplicar o no se confirmo la configuracion."
    restore_all
    exit 1
fi

# ---------------------------------------------------------
# 9. Interfaces no seleccionadas: down + flush en caliente
# ---------------------------------------------------------

if [ "$clean_unused" = true ]; then
    echo ""
    echo "Dejando en DOWN las interfaces no seleccionadas..."
    for iface in "${unused[@]}"; do
        echo "Bajando y limpiando: $iface"
        if command -v nmcli >/dev/null 2>&1 && systemctl is-active --quiet NetworkManager; then
            nmcli device set "$iface" managed no 2>/dev/null \
                || echo "AVISO: nmcli no pudo marcar $iface como no gestionada"
        fi
        ip addr flush dev "$iface"      || echo "AVISO: flush de IPs fallo en $iface"
        ip -6 addr flush dev "$iface"   2>/dev/null
        ip -4 route flush dev "$iface"  2>/dev/null
        ip link set "$iface" down       || echo "ERROR al bajar $iface"
        echo "  -> $(ip -br link show "$iface")"
    done
fi

# Kea: comprobar interfaces/IP y reiniciar si procede
kea_post_check

# ---------------------------------------------------------
# 10. Resultado
# ---------------------------------------------------------

echo ""
echo "=== ESTADO ACTUAL ==="
ip -br addr
echo ""
ip route
echo ""
echo "Backups en: $BACKUP_DIR"
echo ""
echo "=========================================="
echo " CONFIGURACION APLICADA CORRECTAMENTE"
echo "=========================================="
echo ""
echo "Fin del script."
__EOF_RED_UBUNTU__
}

emit_red_debian() {
cat <<'__EOF_RED_DEBIAN__'
#!/bin/bash

# ==========================================================
# CONFIGURACION DE RED - DEBIAN / NETWORKING (ifupdown)
# Practica de Servicios de Red  -  VERSION CORREGIDA v3 (compatible con kea-dhcp-manager.sh)
# ==========================================================

CONFIG_FILE="/etc/network/interfaces"
NM_UNMANAGED_FILE="/etc/NetworkManager/conf.d/99-unmanaged-unused.conf"
KEA_CONF="${KEA_CONF:-/etc/kea/kea-dhcp4.conf}"   # mismo fichero que usa kea-dhcp-manager.sh
TEMP_FILE="$(mktemp /tmp/interfaces.new.XXXXXX)"
IFUP_TIMEOUT=60

trap 'rm -f "$TEMP_FILE"' EXIT

# ---------------------------------------------------------
# Funciones auxiliares
# ---------------------------------------------------------

valid_ip() {
    local ip=$1 IFS=. o x
    [[ $ip =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 1
    read -ra o <<< "$ip"
    for x in "${o[@]}"; do
        (( 10#$x <= 255 )) || return 1
    done
    return 0
}

# Acepta mascara con puntos (255.255.255.0) o prefijo (24)
valid_mask() {
    local m=$1
    if [[ $m =~ ^[0-9]{1,2}$ ]]; then
        (( 10#$m >= 1 && 10#$m <= 32 )) && return 0
        return 1
    fi
    valid_ip "$m" || return 1
    local IFS=. a b c d bin inv
    read -r a b c d <<< "$m"
    bin=$(( (10#$a << 24) | (10#$b << 16) | (10#$c << 8) | 10#$d ))
    (( bin == 0 )) && return 1            # 0.0.0.0 no es valida
    inv=$(( ~bin & 0xFFFFFFFF ))
    (( (inv & (inv + 1)) == 0 ))
}

ask_yes_no() {
    local prompt=$1 ans
    while true; do
        read -rp "$prompt (s/n): " ans || exit 1
        case "$ans" in
            [sS]) return 0 ;;
            [nN]) return 1 ;;
            *) echo "Responde s o n." ;;
        esac
    done
}

ask_ip() {
    local prompt=$1 val
    while true; do
        read -rp "$prompt: " val || exit 1
        if valid_ip "$val"; then
            REPLY_VALUE=$(norm_ip "$val")
            return 0
        fi
        echo "IP no valida, intentalo de nuevo."
    done
}

ask_mask() {
    local val
    while true; do
        read -rp "Introduce la mascara (ej: 255.255.255.0 o 24): " val || exit 1
        if valid_mask "$val"; then
            # Normaliza prefijos con cero a la izquierda (08 -> 8)
            [[ $val =~ ^[0-9]{1,2}$ ]] && val=$((10#$val))
            REPLY_VALUE=$val
            return 0
        fi
        echo "Mascara no valida, intentalo de nuevo."
    done
}

ip2int() {
    local IFS=. a b c d
    read -r a b c d <<< "$1"
    echo $(( (10#$a << 24) | (10#$b << 16) | (10#$c << 8) | 10#$d ))
}

int2ip() {
    local n=$1
    echo "$(( (n >> 24) & 255 )).$(( (n >> 16) & 255 )).$(( (n >> 8) & 255 )).$(( n & 255 ))"
}

# Normaliza una IP a decimal (quita ceros a la izquierda: 192.168.001.010 -> 192.168.1.10)
# Evita que "010" se interprete como octal al aplicarse la IP.
norm_ip() { int2ip "$(ip2int "$1")"; }

# mask2prefix 255.255.255.0 -> 24   (si ya es un numero, lo devuelve tal cual)
mask2prefix() {
    local m=$1 n c=0
    if [[ $m =~ ^[0-9]{1,2}$ ]]; then
        echo $((10#$m))
        return
    fi
    n=$(ip2int "$m")
    while (( n & 0x80000000 )); do
        c=$((c + 1))
        n=$(( (n << 1) & 0xFFFFFFFF ))
    done
    echo "$c"
}

# net_addr IP PREFIJO  ->  direccion de red (ej: 192.168.1.0)
net_addr() {
    local ipn mask p=$2
    ipn=$(ip2int "$1")
    mask=$(( (0xFFFFFFFF << (32 - p)) & 0xFFFFFFFF ))
    int2ip $(( ipn & mask ))
}

# Pide la gateway y comprueba que pertenece a la subred de la interfaz
ask_gateway() {
    local ip_addr=$1 prefix=$2 gw
    while true; do
        ask_ip "Introduce la gateway"
        gw=$REPLY_VALUE
        if [ "$(net_addr "$gw" "$prefix")" = "$(net_addr "$ip_addr" "$prefix")" ]; then
            return 0
        fi
        echo "AVISO: la gateway $gw NO esta en la red $(net_addr "$ip_addr" "$prefix")/$prefix."
        if ask_yes_no "Usarla igualmente?"; then
            return 0
        fi
    done
}

# Baja una interfaz con la config ANTIGUA y la deja limpia:
# ifdown, mata dhclient residual, flush de IPs y rutas.
down_and_flush() {
    local iface=$1
    timeout 20 ifdown --force "$iface" >/dev/null 2>&1
    pkill -f "dhclient.*[[:space:]]${iface}\$" 2>/dev/null
    ip addr flush dev "$iface" || echo "AVISO: flush de IPs fallo en $iface"
    ip -4 route flush dev "$iface" 2>/dev/null
    ip -6 addr flush dev "$iface" 2>/dev/null
}

# --- Integracion con kea-dhcp-manager.sh ---------------------------------
# Kea escucha en "interfaz/IP" concretas. Devuelve esas referencias leidas de
# su fichero de configuracion (una por linea, formato ens33/150.160.229.200).
kea_refs() {
    [ -f "$KEA_CONF" ] || return 0
    grep -oE '"[A-Za-z0-9._-]+/[0-9]{1,3}(\.[0-9]{1,3}){3}"' "$KEA_CONF" | tr -d '"' | sort -u
}

kea_service_name() {
    local s
    for s in kea-dhcp4-server kea-dhcp4; do
        if systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "^$s.service"; then
            echo "$s"; return 0
        fi
    done
    return 1
}

# Tras cambiar la red: comprueba que Kea sigue teniendo sus interfaces/IP y lo reinicia
kea_post_check() {
    local svc ref ifc ip missing=false
    svc=$(kea_service_name) || svc=""
    [ -z "$svc" ] && [ ! -f "$KEA_CONF" ] && return 0

    echo ""
    echo "=== COMPROBACION CON KEA DHCP ==="
    mapfile -t kea_ref_list < <(kea_refs)
    for ref in "${kea_ref_list[@]}"; do
        [ -z "$ref" ] && continue
        ifc=${ref%%/*}; ip=${ref#*/}
        if ip -o -4 addr show dev "$ifc" 2>/dev/null | grep -qE "inet ${ip//./\\.}/"; then
            echo "  OK     $ifc tiene $ip"
        else
            echo "  FALTA  $ifc NO tiene $ip (Kea esta configurado para usarla)"
            missing=true
        fi
    done

    if [ "$missing" = true ]; then
        echo ""
        echo "AVISO: Kea apunta a interfaces/IP que ya no existen. No se reinicia."
        echo "       Ajusta Kea con: kea-dhcp-manager.sh -> opcion 3 -> 8 (Interfaz / IP)"
        echo "       y despues opcion 6 (APLICAR)."
        return 0
    fi

    if [ -n "$svc" ] && systemctl is-active --quiet "$svc"; then
        echo "Reiniciando $svc para que reabra sus sockets con las IP actuales..."
        if timeout 30 systemctl restart "$svc"; then
            echo "  $svc reiniciado correctamente."
        else
            echo "  AVISO: $svc no arranco. Revisa: journalctl -u $svc --no-pager -n 30"
        fi
    fi
}

# ---------------------------------------------------------
# Comprobaciones iniciales
# ---------------------------------------------------------

echo "=========================================="
echo "   CONFIGURACION DE RED - DEBIAN"
echo "=========================================="
echo ""

if [ "$EUID" -ne 0 ]; then
    echo "ERROR: Este script debe ejecutarse como root."
    echo "Ejecutalo con: sudo $0"
    exit 1
fi

if ! command -v ifup >/dev/null 2>&1; then
    echo "ERROR: ifupdown no esta instalado (apt install ifupdown)."
    exit 1
fi

echo "=== GESTORES DE RED ==="
for svc in NetworkManager systemd-networkd networking; do
    printf "%-20s %s\n" "$svc" "$(systemctl is-active "$svc" 2>/dev/null)"
done
echo ""

NM_ACTIVE=false
if systemctl is-active --quiet NetworkManager; then
    NM_ACTIVE=true
    echo "AVISO: NetworkManager esta activo. El script marcara como 'no gestionadas'"
    echo "       las interfaces que decidas limpiar para que no las vuelva a levantar."
    echo ""
fi

# ---------------------------------------------------------
# Deteccion de interfaces
# ---------------------------------------------------------

echo "=== INTERFACES DETECTADAS ==="
echo ""

interfaces=()

for iface_path in /sys/class/net/*; do
    iface=$(basename "$iface_path")
    [ "$iface" = "lo" ] && continue
    [ -e "$iface_path/device" ] || continue     # solo interfaces fisicas
    [ -d "$iface_path/wireless" ] && continue   # sin wifi
    interfaces+=("$iface")
    echo " - $iface"
done

echo ""

if [ "${#interfaces[@]}" -eq 0 ]; then
    echo "ERROR: No se han encontrado interfaces de red fisicas."
    exit 1
fi

while true; do
    read -rp "Cuantas interfaces quieres configurar?: " num_ifaces || exit 1
    if [[ "$num_ifaces" =~ ^[0-9]+$ ]] && [ "$num_ifaces" -ge 1 ] \
       && [ "$num_ifaces" -le "${#interfaces[@]}" ]; then
        break
    fi
    echo "Introduce un numero entre 1 y ${#interfaces[@]}."
done

# ---------------------------------------------------------
# Seleccion de interfaces
# ---------------------------------------------------------

used_interfaces=()
echo ""

for ((i=0; i<num_ifaces; i++)); do
    while true; do
        echo "Interfaces disponibles:"
        for ((j=0; j<${#interfaces[@]}; j++)); do
            echo "$((j+1))) ${interfaces[$j]}"
        done
        echo ""

        read -rp "Selecciona el numero de la interfaz $((i+1)): " selection || exit 1

        if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] \
           || [ "$selection" -gt "${#interfaces[@]}" ]; then
            echo "Seleccion no valida."
            echo ""
            continue
        fi

        iface="${interfaces[$((selection-1))]}"

        already_used=false
        for used in "${used_interfaces[@]}"; do
            [ "$used" = "$iface" ] && already_used=true
        done

        if [ "$already_used" = true ]; then
            echo "Esa interfaz ya ha sido seleccionada."
            echo ""
            continue
        fi

        used_interfaces+=("$iface")
        echo "Interfaz seleccionada: $iface"
        echo ""
        break
    done
done

# ---------------------------------------------------------
# Generacion del fichero (NO se toca el sistema todavia)
# ---------------------------------------------------------

{
    echo "# Generado por conf-red-debian-ensX.sh"
    echo "auto lo"
    echo "iface lo inet loopback"
    echo ""
} > "$TEMP_FILE"

dns_all=""
idx=0

# Policy routing: solo tiene sentido con 2 o mas interfaces
policy_routing=false
if [ "${#used_interfaces[@]}" -gt 1 ]; then
    echo "Con varias redes, las respuestas de las tarjetas secundarias pueden"
    echo "salir por la gateway de otra tarjeta y fallar (enrutamiento asimetrico)."
    echo "Con 'policy routing' cada tarjeta responde siempre por su propia gateway."
    echo ""
    if ask_yes_no "Activar policy routing para las interfaces estaticas con gateway? (recomendado)"; then
        policy_routing=true
    fi
    echo ""
fi

for iface in "${used_interfaces[@]}"; do

    idx=$((idx + 1))
    metric=$((idx * 100))      # 100, 200, 300... (menor = preferida)
    table=$((100 + idx))       # tabla de rutas propia (101, 102...)

    echo "=========================================="
    echo "Configurando interfaz: $iface"
    echo "=========================================="

    echo "auto $iface" >> "$TEMP_FILE"

    if ask_yes_no "Quieres usar DHCP en $iface?"; then

        echo "iface $iface inet dhcp" >> "$TEMP_FILE"
        # Metrica propia para que su gateway no choque con las de otras tarjetas
        echo "    metric $metric" >> "$TEMP_FILE"

    else

        echo "iface $iface inet static" >> "$TEMP_FILE"

        ask_ip "Introduce la IP"
        ip_addr=$REPLY_VALUE
        ask_mask
        mask=$REPLY_VALUE

        echo "    address $ip_addr" >> "$TEMP_FILE"
        # ifupdown acepta tanto 255.255.255.0 como el prefijo (24)
        echo "    netmask $mask" >> "$TEMP_FILE"
        prefix=$(mask2prefix "$mask")

        if ask_yes_no "Quieres configurar DNS estatico?"; then
            while true; do
                read -rp "Introduce DNS separados por espacio: " dns || exit 1
                ok=true
                for d in $dns; do
                    valid_ip "$d" || ok=false
                done
                if [ "$ok" = true ] && [ -n "${dns// /}" ]; then
                    break
                fi
                echo "Alguna IP de DNS no es valida."
            done
            dns=$(for d in $dns; do norm_ip "$d"; done | paste -sd" " -)   # normaliza espacios y ceros
            echo "    dns-nameservers $dns" >> "$TEMP_FILE"
            dns_all="$dns_all $dns"
        fi

        # Cada interfaz puede tener SU gateway (con metrica distinta, para
        # evitar "RTNETLINK answers: File exists")
        if ask_yes_no "Tiene gateway?"; then
            ask_gateway "$ip_addr" "$prefix"
            gateway=$REPLY_VALUE
            network=$(net_addr "$ip_addr" "$prefix")

            echo "    gateway $gateway" >> "$TEMP_FILE"
            echo "    metric $metric" >> "$TEMP_FILE"

            if [ "$policy_routing" = true ]; then
                # Tabla propia: el trafico originado en esta IP sale por esta gateway
                echo "    post-up ip route replace $network/$prefix dev $iface src $ip_addr table $table" >> "$TEMP_FILE"
                echo "    post-up ip route replace default via $gateway dev $iface table $table" >> "$TEMP_FILE"
                echo "    post-up ip rule add from $ip_addr/32 table $table || true" >> "$TEMP_FILE"
                echo "    pre-down ip rule del from $ip_addr/32 table $table || true" >> "$TEMP_FILE"
                echo "    pre-down ip route flush table $table || true" >> "$TEMP_FILE"
            fi
        fi
    fi

    echo "" >> "$TEMP_FILE"
    echo ""
done

# ---------------------------------------------------------
# Preview y confirmacion
# ---------------------------------------------------------

echo "=========================================="
echo "       PREVIEW DE $CONFIG_FILE"
echo "=========================================="
cat "$TEMP_FILE"
echo "=========================================="
echo ""

# Interfaces no seleccionadas (se calculan y se preguntan AHORA, para poder
# bajarlas mientras el fichero antiguo sigue en su sitio)
unused=()
for iface in "${interfaces[@]}"; do
    in_use=false
    for used in "${used_interfaces[@]}"; do
        [ "$iface" = "$used" ] && in_use=true
    done
    [ "$in_use" = false ] && unused+=("$iface")
done

clean_unused=false
if [ "${#unused[@]}" -gt 0 ]; then
    echo "Interfaces NO seleccionadas: ${unused[*]}"
    if ask_yes_no "Quieres bajarlas y limpiarlas (down + flush)?"; then
        clean_unused=true
    fi
    echo ""
fi

if [ "$clean_unused" = true ]; then
    # Array (no "while read < <(...)"): asi ask_yes_no sigue leyendo del teclado
    mapfile -t kea_ref_list < <(kea_refs)
    for ref in "${kea_ref_list[@]}"; do
        [ -z "$ref" ] && continue
        for iface in "${unused[@]}"; do
            if [ "${ref%%/*}" = "$iface" ]; then
                echo "AVISO: Kea DHCP esta configurado para escuchar en $iface (${ref#*/})"
                echo "       y vas a bajarla: Kea dejaria de dar servicio en esa red."
                ask_yes_no "Continuar igualmente?" || { echo "Operacion cancelada."; exit 0; }
            fi
        done
    done
fi

if ! ask_yes_no "Quieres aplicar esta configuracion de forma permanente?"; then
    echo ""
    echo "Operacion cancelada. No se ha modificado nada."
    exit 0
fi

# ---------------------------------------------------------
# Aplicacion
# ---------------------------------------------------------

BACKUP=""
if [ -f "$CONFIG_FILE" ]; then
    BACKUP="${CONFIG_FILE}.bak.$(date +%Y%m%d-%H%M%S)"
    if ! cp "$CONFIG_FILE" "$BACKUP"; then
        echo "ERROR al crear el backup."
        exit 1
    fi
    echo "Backup guardado en: $BACKUP"
fi

# Si NetworkManager esta activo, marcar YA las no seleccionadas como no gestionadas
if [ "$clean_unused" = true ] && [ "$NM_ACTIVE" = true ] && command -v nmcli >/dev/null 2>&1; then
    for iface in "${unused[@]}"; do
        nmcli device set "$iface" managed no 2>/dev/null \
            || echo "AVISO: nmcli no pudo marcar $iface como no gestionada"
    done
    # Persistente (sobrevive a reinicios)
    if [ -d /etc/NetworkManager/conf.d ]; then
        {
            echo "[keyfile]"
            list=""
            for iface in "${unused[@]}"; do
                list="${list}interface-name:${iface};"
            done
            echo "unmanaged-devices=${list%;}"
        } > "$NM_UNMANAGED_FILE"
        nmcli general reload conf 2>/dev/null
    fi
fi

echo ""
echo "Bajando y limpiando interfaces (con la config ANTIGUA)..."
to_down=("${used_interfaces[@]}")
[ "$clean_unused" = true ] && to_down+=("${unused[@]}")

for iface in "${to_down[@]}"; do
    echo " - $iface"
    down_and_flush "$iface"
done

if ! cp "$TEMP_FILE" "$CONFIG_FILE"; then
    echo "ERROR al copiar la configuracion."
    if [ -n "$BACKUP" ]; then
        cp "$BACKUP" "$CONFIG_FILE"
        timeout 120 systemctl restart networking
        echo "Configuracion anterior restaurada."
    fi
    exit 1
fi

echo ""
echo "Levantando interfaces con la configuracion nueva..."
failed=false

for iface in "${used_interfaces[@]}"; do
    echo "--> ifup $iface (maximo ${IFUP_TIMEOUT}s)"
    if timeout "$IFUP_TIMEOUT" ifup "$iface"; then
        echo "    OK"
    else
        echo "    ERROR: fallo ifup en $iface"
        failed=true
    fi
done

# Interfaces no seleccionadas: down definitivo (despues del ifup de las demas)
if [ "$clean_unused" = true ]; then
    echo ""
    echo "Dejando en DOWN las interfaces no seleccionadas..."
    for iface in "${unused[@]}"; do
        # Segunda pasada por si algo las ha vuelto a levantar
        pkill -f "dhclient.*[[:space:]]${iface}\$" 2>/dev/null
        ip addr flush dev "$iface" || echo "AVISO: flush fallo en $iface"
        ip -4 route flush dev "$iface" 2>/dev/null
        ip link set "$iface" down || echo "ERROR al bajar $iface"
        echo "  -> $(ip -br link show "$iface")"
    done
fi

# DNS: dns-nameservers solo funciona con resolvconf
if [ -n "${dns_all// /}" ] && ! command -v resolvconf >/dev/null 2>&1; then
    echo ""
    echo "resolvconf no esta instalado: escribo los DNS en /etc/resolv.conf"
    [ -e /etc/resolv.conf ] && cp -L /etc/resolv.conf "/etc/resolv.conf.bak.$(date +%Y%m%d-%H%M%S)"
    [ -L /etc/resolv.conf ] && rm -f /etc/resolv.conf   # no escribir a traves de un enlace
    # sin duplicados y maximo 3 (limite de glibc)
    printf 'nameserver %s\n' $(printf '%s\n' $dns_all | awk '!s[$0]++' | head -3) > /etc/resolv.conf
fi

# Kea: comprobar interfaces/IP y reiniciar si procede
if [ "$failed" != true ]; then
    kea_post_check
fi

# ---------------------------------------------------------
# Resultado
# ---------------------------------------------------------

echo ""
echo "=== ESTADO ACTUAL ==="
ip -br addr
echo ""
ip route
echo ""

if [ "$failed" = true ]; then
    echo "=========================================="
    echo " HUBO ERRORES AL LEVANTAR INTERFACES"
    echo "=========================================="
    if [ -n "$BACKUP" ] && ask_yes_no "Quieres restaurar el backup anterior?"; then
        # Bajar primero lo nuevo, mientras el fichero nuevo sigue puesto
        for iface in "${used_interfaces[@]}"; do
            timeout 20 ifdown --force "$iface" >/dev/null 2>&1
            ip addr flush dev "$iface" 2>/dev/null
        done
        cp "$BACKUP" "$CONFIG_FILE"
        timeout 120 systemctl restart networking
        echo "Backup restaurado."
    else
        echo "Revisa $CONFIG_FILE y los mensajes de arriba."
    fi
    exit 1
fi

echo "=========================================="
echo " CONFIGURACION APLICADA CORRECTAMENTE"
echo "=========================================="
echo ""
echo "Fin del script."
__EOF_RED_DEBIAN__
}

emit_kea_manager() {
cat <<'__EOF_KEA_MANAGER__'
#!/bin/bash
#=============================================================================
#  kea-dhcp-manager.sh
#  Gestor interactivo (100% OFFLINE) de un servidor DHCP con KEA (DHCPv4).
#
#  - Crea / modifica /etc/kea/kea-dhcp4.conf a partir de una "base de datos"
#    de redes guardada en texto plano (/etc/kea/kea-manager/).
#  - Escalable: tantas redes (subnets) como interfaces/subredes necesites.
#  - Por red: rango, exclusiones (IPs, rangos, pares, impares), tiempo de
#    concesion, gateway, DNS, dominio, search y reservas por MAC.
#  - Calcula solo los "pools" de KEA: rango - exclusiones - reservas.
#
#  Uso:   sudo ./kea-dhcp-manager.sh            (menu interactivo)
#         sudo ./kea-dhcp-manager.sh --generate (imprime el JSON generado)
#         sudo ./kea-dhcp-manager.sh --apply    (valida, aplica y reinicia)
#
#  Variables opcionales:
#     KEA_CONF=/etc/kea/kea-dhcp4.conf   KEA_MGR_DIR=/etc/kea/kea-manager
#     KEA_LEASES=/var/lib/kea/kea-leases4.csv
#
#  Solo usa utilidades base (bash >= 4.2, ip, sed, date, systemctl).
#  No necesita internet, ni jq, ni python.
#=============================================================================

DB_DIR="${KEA_MGR_DIR:-/etc/kea/kea-manager}"
CONF="${KEA_CONF:-/etc/kea/kea-dhcp4.conf}"
LEASES_FILE="${KEA_LEASES:-/var/lib/kea/kea-leases4.csv}"

# ---------------------------------------------------------------- utilidades
if [[ -t 1 ]]; then
  R=$'\e[31m'; G=$'\e[32m'; Y=$'\e[33m'; B=$'\e[36m'; N=$'\e[0m'
else R=; G=; Y=; B=; N=; fi
err()  { echo "${R}[ERROR]${N} $*" >&2; }
warn() { echo "${Y}[AVISO]${N} $*" >&2; }
ok()   { echo "${G}[ OK ]${N} $*"; }
info() { echo "${B}[INFO]${N} $*"; }
pause() { read -rp "Pulsa ENTER para continuar..." _; }

if (( BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 2) )); then
  err "Se necesita bash >= 4.2"; exit 1
fi

init_db() {
  mkdir -p "$DB_DIR" 2>/dev/null || { err "No puedo crear $DB_DIR (¿eres root?)"; exit 1; }
}

# ------------------------------------------------------- IP / red (aritmetica)
ip2int() {
  local a b c d
  IFS=. read -r a b c d <<<"$1"
  echo $(( (10#$a << 24) | (10#$b << 16) | (10#$c << 8) | 10#$d ))
}
int2ip() { local n=$1; echo "$(( (n>>24)&255 )).$(( (n>>16)&255 )).$(( (n>>8)&255 )).$(( n&255 ))"; }

valid_ip() {
  [[ $1 =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]] || return 1
  local i
  for i in 1 2 3 4; do (( 10#${BASH_REMATCH[i]} <= 255 )) || return 1; done
}
valid_cidr() {
  [[ $1 =~ ^([0-9.]+)/([0-9]{1,2})$ ]] || return 1
  local _ip=${BASH_REMATCH[1]} _p=${BASH_REMATCH[2]}   # guardar ANTES de llamar a valid_ip
  valid_ip "$_ip" || return 1
  (( 10#$_p >= 8 && 10#$_p <= 30 ))
}
mask_of() { echo $(( (0xFFFFFFFF << (32 - 10#$1)) & 0xFFFFFFFF )); }
net_int() { local ip=${1%/*} p=${1#*/}; echo $(( $(ip2int "$ip") & $(mask_of "$p") )); }
bcast_int() { local p=${1#*/}; echo $(( $(net_int "$1") | (~$(mask_of "$p") & 0xFFFFFFFF) )); }
in_subnet() {  # in_subnet IP CIDR
  local n; n=$(ip2int "$1")
  (( (n & $(mask_of "${2#*/}")) == $(net_int "$2") ))
}
# Acepta "181" (host dentro de la subred) o una IP completa
resolve_ip() {
  local in=$1 cidr=$2
  if [[ $in =~ ^[0-9]+$ ]]; then
    int2ip $(( $(net_int "$cidr") + 10#$in ))
  elif valid_ip "$in"; then
    echo "$in"
  else
    return 1
  fi
}
valid_mac() { [[ $1 =~ ^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$ ]]; }
norm_mac()  { local m=${1,,}; echo "${m//-/:}"; }
valid_host() { [[ $1 =~ ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ ]]; }

# ---------------------------------------------------------- tiempos de lease
parse_time() {  # 1h, 30m, 2d, 3600
  [[ $1 =~ ^([0-9]+)([smhdSMHD]?)$ ]] || return 1
  local n=$((10#${BASH_REMATCH[1]})) u=${BASH_REMATCH[2],,}
  case $u in
    ""|s) echo "$n" ;; m) echo $((n*60)) ;; h) echo $((n*3600)) ;; d) echo $((n*86400)) ;;
  esac
}
fmt_time() {
  local s=$1
  if   (( s % 86400 == 0 )); then echo "$((s/86400))d"
  elif (( s % 3600  == 0 )); then echo "$((s/3600))h"
  elif (( s % 60    == 0 )); then echo "$((s/60))m"
  else echo "${s}s"; fi
}

# --------------------------------------------------------- base de datos redes
NET_VARS=(ID NAME SUBNET IFACE SERVER_IP RS RE EXCL LEASE GW DNS DOMAIN SEARCH)

list_ids() {
  local f
  for f in "$DB_DIR"/net_*.conf; do
    [[ -e $f ]] || continue
    f=${f##*/}; f=${f#net_}; echo "${f%.conf}"
  done | sort -n
}
next_id() { local m=0 i; for i in $(list_ids); do (( i > m )) && m=$i; done; echo $((m+1)); }
clear_net() { local v; for v in "${NET_VARS[@]}"; do printf -v "$v" '%s' ""; done; }
load_net() { clear_net; # shellcheck disable=SC1090
  source "$DB_DIR/net_$1.conf"; }
save_net() {
  local v
  { for v in "${NET_VARS[@]}"; do printf '%s=%q\n' "$v" "${!v}"; done; } > "$DB_DIR/net_$ID.conf"
  touch "$DB_DIR/net_$ID.res"
}
res_file() { echo "$DB_DIR/net_$ID.res"; }

# ------------------------------------------------- deteccion de interfaz / IP
detect_iface() {  # usa $SUBNET, rellena IFACE y SERVER_IP
  command -v ip >/dev/null || return 1
  local line ifc cidr ip
  while read -r line; do
    read -r _ ifc _ cidr _ <<<"$line"
    ip=${cidr%/*}
    valid_ip "$ip" || continue
    if in_subnet "$ip" "$SUBNET"; then IFACE=$ifc; SERVER_IP=$ip; return 0; fi
  done < <(ip -o -4 addr show 2>/dev/null)
  return 1
}

# ----------------------------------------------------------- entradas/prompts
# ask_ip VAR "texto" default [opt]  -> acepta "181" o IP completa; "-" vacia si opt
ask_ip() {
  local __v=$1 p=$2 d=$3 opt=$4 ans r shown=${3:-ninguno}
  while true; do
    read -rp "$p [$shown]: " ans; ans=${ans:-$d}
    if [[ $ans == "-" && -n $opt ]]; then printf -v "$__v" '%s' ""; return; fi
    if [[ -z $ans && -n $opt ]]; then printf -v "$__v" '%s' ""; return; fi
    if r=$(resolve_ip "$ans" "$SUBNET") && in_subnet "$r" "$SUBNET"; then
      printf -v "$__v" '%s' "$r"; return
    fi
    err "IP no valida o fuera de la subred $SUBNET"
  done
}
# ask_iplist VAR "texto" default  -> lista de IPs separadas por coma/;/espacio
ask_iplist() {
  local __v=$1 p=$2 d=$3 ans t ok_ out
  while true; do
    read -rp "$p [${d:-ninguno}]: " ans; ans=${ans:-$d}
    [[ $ans == "-" ]] && ans=""
    ok_=1; out=()
    for t in ${ans//[,;]/ }; do valid_ip "$t" && out+=("$t") || ok_=0; done
    if (( ok_ )); then
      local IFS=,; out="${out[*]}"; printf -v "$__v" '%s' "${out//,/, }"; return
    fi
    err "Alguna IP no es valida"
  done
}
ask_domain() {  # ask_domain VAR texto default
  local __v=$1 p=$2 d=$3 ans
  while true; do
    read -rp "$p [${d:-ninguno}]: " ans; ans=${ans:-$d}
    [[ $ans == "-" ]] && ans=""
    if [[ -z $ans ]] || valid_host "$ans"; then printf -v "$__v" '%s' "$ans"; return; fi
    err "Nombre de dominio no valido"
  done
}
ask_search() {  # lista de dominios
  local __v=$1 p=$2 d=$3 ans t ok_ out
  while true; do
    read -rp "$p [${d:-ninguno}]: " ans; ans=${ans:-$d}
    [[ $ans == "-" ]] && ans=""
    ok_=1; out=()
    for t in ${ans//[,;]/ }; do valid_host "$t" && out+=("$t") || ok_=0; done
    if (( ok_ )); then
      local IFS=,; out="${out[*]}"; printf -v "$__v" '%s' "${out//,/, }"; return
    fi
    err "Algun dominio no es valido"
  done
}
ask_time() {
  local __v=$1 p=$2 d=$3 ans s
  while true; do
    read -rp "$p [$(fmt_time "$d")]  (ej: 1h, 30m, 2d, 7200): " ans
    if [[ -z $ans ]]; then printf -v "$__v" '%s' "$d"; return; fi
    if s=$(parse_time "$ans") && (( s >= 60 )); then printf -v "$__v" '%s' "$s"; return; fi
    err "Tiempo no valido (minimo 1m)"
  done
}
ask_range() {
  local rs re
  while true; do
    ask_ip rs "IP inicial del rango (host o IP completa)" "$RS"
    ask_ip re "IP final del rango   (host o IP completa)" "$RE"
    if (( $(ip2int "$rs") <= $(ip2int "$re") )); then RS=$rs; RE=$re; return; fi
    err "La IP inicial debe ser menor o igual que la final"
  done
}
ask_excl() {
  echo "  Formatos admitidos (separados por comas):"
  echo "    pares | impares | 125 | 125-150 | 1.1.1.30 | 1.1.1.30-1.1.1.40 | ninguna"
  local ans
  while true; do
    read -rp "Exclusiones [${EXCL:-ninguna}]: " ans; ans=${ans:-$EXCL}
    [[ $ans == "-" || ${ans,,} == "ninguna" ]] && ans=""
    if check_excl "$ans"; then EXCL=$ans; return; fi
    err "Exclusion no valida"
  done
}
check_excl() {
  local tok a b
  for tok in ${1//,/ }; do
    tok=${tok,,}
    case $tok in
      pares|par|even|impares|impar|odd|ninguna|none) ;;
      *-*) a=$(resolve_ip "${tok%-*}" "$SUBNET") && b=$(resolve_ip "${tok#*-}" "$SUBNET") || return 1 ;;
      *)   resolve_ip "$tok" "$SUBNET" >/dev/null || return 1 ;;
    esac
  done
  return 0
}

# ---------------------------------------- calculo de pools (rango - exclusiones)
declare -gA EXSET RESSET
build_sets() {
  unset EXSET RESSET; declare -gA EXSET RESSET
  local rs re i tok a b line
  rs=$(ip2int "$RS"); re=$(ip2int "$RE")
  for tok in ${EXCL//,/ }; do
    tok=${tok,,}
    case $tok in
      pares|par|even)     for ((i=rs;i<=re;i++)); do (( (i&1)==0 )) && EXSET[$i]=1; done ;;
      impares|impar|odd)  for ((i=rs;i<=re;i++)); do (( (i&1)==1 )) && EXSET[$i]=1; done ;;
      ninguna|none) ;;
      *-*) a=$(ip2int "$(resolve_ip "${tok%-*}" "$SUBNET")"); b=$(ip2int "$(resolve_ip "${tok#*-}" "$SUBNET")")
           for ((i=a;i<=b;i++)); do EXSET[$i]=1; done ;;
      *)   EXSET[$(ip2int "$(resolve_ip "$tok" "$SUBNET")")]=1 ;;
    esac
  done
  if [[ -s $(res_file) ]]; then
    while IFS='|' read -r _ a _; do [[ -n $a ]] && RESSET[$(ip2int "$a")]=1; done < "$(res_file)"
  fi
}
build_pools() {
  build_sets
  local rs re i start="" prev="" nint bint sint=-1
  rs=$(ip2int "$RS"); re=$(ip2int "$RE")
  nint=$(net_int "$SUBNET"); bint=$(bcast_int "$SUBNET")
  [[ -n $SERVER_IP ]] && sint=$(ip2int "$SERVER_IP")
  [[ -n $GW ]] && EXSET[$(ip2int "$GW")]=1
  flush() { [[ -n $start ]] && echo "$(int2ip "$start") - $(int2ip "$prev")"; start=""; }
  for ((i=rs;i<=re;i++)); do
    if [[ -n ${EXSET[$i]} || -n ${RESSET[$i]} ]] || (( i==nint || i==bint || i==sint )); then
      flush
    else
      [[ -z $start ]] && start=$i
      prev=$i
    fi
  done
  flush
}

# --------------------------------------------------------- generador de JSON
gen_json() {
  local ids id seen=() ifs=() entry sep
  mapfile -t ids < <(list_ids)
  declare -A seen_if
  for id in "${ids[@]}"; do
    load_net "$id"
    [[ -z $IFACE ]] && continue
    entry=$IFACE; [[ -n $SERVER_IP ]] && entry="$IFACE/$SERVER_IP"
    [[ -n ${seen_if[$entry]} ]] && continue
    seen_if[$entry]=1; ifs+=("\"$entry\"")
  done
  (( ${#ifs[@]} == 0 )) && ifs=('"*"')

  echo '{'
  echo '    "Dhcp4": {'
  echo '        "interfaces-config": {'
  printf '            "interfaces": [ %s ]\n' "$(IFS=,; echo "${ifs[*]}" | sed 's/,/, /g')"
  echo '        },'
  echo '        "lease-database": {'
  echo '            "type": "memfile",'
  echo '            "persist": true,'
  echo '            "name": "/var/lib/kea/kea-leases4.csv"'
  echo '        },'
  echo '        "subnet4": ['
  local n=${#ids[@]} k=0
  for id in "${ids[@]}"; do
    k=$((k+1)); sep=","; (( k == n )) && sep=""
    load_net "$id"
    gen_subnet
    echo "        }$sep"
  done
  echo '        ],'
  echo '        "loggers": ['
  echo '            {'
  echo '                "name": "kea-dhcp4",'
  echo '                "output_options": [ { "output": "stdout" } ],'
  echo '                "severity": "INFO"'
  echo '            }'
  echo '        ]'
  echo '    }'
  echo '}'
}
gen_subnet() {
  local POOLS opts=() i n sep mac ip host entries=()
  mapfile -t POOLS < <(build_pools)
  echo '        {'
  printf '            "id": %s,\n' "$ID"
  printf '            "subnet": "%s",\n' "$SUBNET"
  [[ -n $IFACE ]] && printf '            "interface": "%s",\n' "$IFACE"
  printf '            "valid-lifetime": %s,\n' "$LEASE"
  echo '            "pools": ['
  n=${#POOLS[@]}
  for ((i=0;i<n;i++)); do
    sep=","; (( i == n-1 )) && sep=""
    printf '                { "pool": "%s" }%s\n' "${POOLS[i]}" "$sep"
  done
  echo '            ],'
  [[ -n $GW ]]     && opts+=("{ \"name\": \"routers\", \"data\": \"$GW\" }")
  [[ -n $DNS ]]    && opts+=("{ \"name\": \"domain-name-servers\", \"data\": \"$DNS\" }")
  [[ -n $DOMAIN ]] && opts+=("{ \"name\": \"domain-name\", \"data\": \"$DOMAIN\" }")
  [[ -n $SEARCH ]] && opts+=("{ \"name\": \"domain-search\", \"data\": \"$SEARCH\" }")
  echo '            "option-data": ['
  n=${#opts[@]}
  for ((i=0;i<n;i++)); do
    sep=","; (( i == n-1 )) && sep=""
    printf '                %s%s\n' "${opts[i]}" "$sep"
  done
  echo '            ],'
  if [[ -s $(res_file) ]]; then
    while IFS='|' read -r mac ip host; do
      [[ -z $mac ]] && continue
      if [[ -n $host ]]; then
        entries+=("$(printf '{\n                    "hw-address": "%s",\n                    "ip-address": "%s",\n                    "hostname": "%s"\n                }' "$mac" "$ip" "$host")")
      else
        entries+=("$(printf '{\n                    "hw-address": "%s",\n                    "ip-address": "%s"\n                }' "$mac" "$ip")")
      fi
    done < "$(res_file)"
  fi
  echo '            "reservations": ['
  n=${#entries[@]}
  for ((i=0;i<n;i++)); do
    sep=","; (( i == n-1 )) && sep=""
    printf '                %s%s\n' "${entries[i]}" "$sep"
  done
  echo '            ]'
}

# ------------------------------------------------------------- mostrar redes
show_net() {
  local cnt=0
  [[ -s $(res_file) ]] && cnt=$(wc -l < "$(res_file)")
  echo "${B}--- Red #$ID: ${NAME:-sin nombre} ---${N}"
  echo "  Subred ........ $SUBNET"
  echo "  Interfaz/IP ... ${IFACE:-?}/${SERVER_IP:-?}"
  echo "  Rango ......... $RS - $RE"
  echo "  Exclusiones ... ${EXCL:-ninguna}"
  echo "  Concesion ..... $(fmt_time "$LEASE")"
  echo "  Gateway ....... ${GW:-ninguno}"
  echo "  DNS ........... ${DNS:-ninguno}"
  echo "  Dominio ....... ${DOMAIN:-ninguno}"
  echo "  Search ........ ${SEARCH:-ninguno}"
  echo "  Reservas ...... $cnt"
  if (( cnt > 0 )); then
    local mac ip host
    while IFS='|' read -r mac ip host; do
      printf '     %-18s -> %-16s %s\n' "$mac" "$ip" "$host"
    done < "$(res_file)"
  fi
  echo "  Pools KEA ..... $(build_pools | paste -sd';' | sed 's/;/  ;  /g')"
}
list_nets() {
  local ids i; mapfile -t ids < <(list_ids)
  if (( ${#ids[@]} == 0 )); then warn "No hay redes definidas."; return 1; fi
  for i in "${ids[@]}"; do load_net "$i"; show_net; echo; done
}
choose_net() {  # deja el id elegido en SEL
  local ids i; mapfile -t ids < <(list_ids)
  if (( ${#ids[@]} == 0 )); then warn "No hay redes definidas."; return 1; fi
  echo "Redes disponibles:"
  for i in "${ids[@]}"; do load_net "$i"; printf '  %s) %-18s %s\n' "$ID" "${NAME:-sin nombre}" "$SUBNET"; done
  local ans
  read -rp "Numero de red: " ans
  [[ -f "$DB_DIR/net_$ans.conf" ]] || { err "Red inexistente"; return 1; }
  SEL=$ans; load_net "$SEL"
}

# ------------------------------------------------------------------ reservas
add_reservation() {
  local mac ip host line
  while true; do
    read -rp "MAC del cliente (aa:bb:cc:dd:ee:ff): " mac
    valid_mac "$mac" && break; err "MAC no valida"
  done
  mac=$(norm_mac "$mac")
  if grep -qi "^$mac|" "$(res_file)" 2>/dev/null; then err "Esa MAC ya tiene reserva"; return; fi
  while true; do
    ask_ip ip "IP a reservar (host o IP completa)" ""
    grep -q "|$ip|" "$(res_file)" 2>/dev/null && { err "Esa IP ya esta reservada"; continue; }
    [[ $ip == "$GW" || $ip == "$SERVER_IP" ]] && { err "Esa IP es la del gateway/servidor"; continue; }
    break
  done
  while true; do
    read -rp "Hostname (opcional, ENTER = ninguno): " host
    [[ -z $host ]] || valid_host "$host" && break
    err "Hostname no valido"
  done
  echo "$mac|$ip|$host" >> "$(res_file)"
  local v; v=$(ip2int "$ip")
  if (( v >= $(ip2int "$RS") && v <= $(ip2int "$RE") )); then
    warn "La IP esta dentro del rango: se excluira automaticamente de los pools."
  fi
  ok "Reserva anadida: $mac -> $ip ${host:+($host)}"
}
menu_reservations() {
  local c n
  while true; do
    echo; echo "${B}== Reservas de la red #$ID ($SUBNET) ==${N}"
    if [[ -s $(res_file) ]]; then nl -w2 -s') ' "$(res_file)" | sed 's/|/  ->  /g'; else echo "  (sin reservas)"; fi
    echo "  a) Anadir   d) Eliminar   v) Volver"
    read -rp "> " c
    case $c in
      a|A) add_reservation ;;
      d|D) read -rp "Numero de linea a eliminar: " n
           if [[ $n =~ ^[0-9]+$ ]] && sed -n "${n}p" "$(res_file)" | grep -q .; then
             sed -i "${n}d" "$(res_file)"; ok "Reserva eliminada"
           else err "Numero no valido"; fi ;;
      v|V) return ;;
    esac
  done
}

# ---------------------------------------------------------- alta / edicion red
new_net() {
  clear_net
  ID=$(next_id)
  echo "${B}== Nueva red (#$ID) ==${N}"
  read -rp "Nombre descriptivo (ej: dhcpuno.org): " NAME
  while true; do
    read -rp "Subred en formato CIDR (ej: 150.160.229.0/24): " SUBNET
    valid_cidr "$SUBNET" || { err "CIDR no valido"; continue; }
    # normaliza a la direccion de red
    SUBNET="$(int2ip "$(net_int "$SUBNET")")/${SUBNET#*/}"; break
  done
  if detect_iface; then
    info "Detectada interfaz $IFACE con IP $SERVER_IP en esta subred."
  else
    warn "Ninguna interfaz local tiene IP en $SUBNET."
  fi
  read -rp "Interfaz del servidor [${IFACE:-eth0}]: " ans; IFACE=${ans:-${IFACE:-eth0}}
  ask_ip SERVER_IP "IP del servidor en esa interfaz" "$SERVER_IP"
  RS=$(int2ip $(( $(net_int "$SUBNET") + 100 ))); RE=$(int2ip $(( $(net_int "$SUBNET") + 200 )))
  ask_range
  ask_excl
  LEASE=3600; ask_time LEASE "Tiempo de concesion" "$LEASE"
  ask_ip GW "Gateway (\"-\" = ninguno)" "" opt
  ask_iplist DNS "Servidores DNS (separados por coma)" ""
  ask_domain DOMAIN "Dominio" ""
  ask_search SEARCH "Dominios search (separados por coma)" ""
  save_net
  ok "Red #$ID guardada."
  read -rp "¿Anadir reservas ahora? [s/N]: " a
  [[ ${a,,} == s* ]] && menu_reservations
  echo; load_net "$ID"; show_net
}
edit_net() {
  choose_net || return
  local c a
  while true; do
    echo; show_net; echo
    echo "  1) Rango de IPs          5) Gateway"
    echo "  2) Exclusiones           6) DNS"
    echo "  3) Reservas              7) Dominio y search"
    echo "  4) Tiempo de concesion   8) Interfaz / IP servidor"
    echo "  9) Nombre                0) Volver"
    read -rp "> " c
    case $c in
      1) ask_range ;;
      2) ask_excl ;;
      3) menu_reservations; continue ;;
      4) ask_time LEASE "Tiempo de concesion" "$LEASE" ;;
      5) ask_ip GW "Gateway (\"-\" = ninguno)" "$GW" opt ;;
      6) ask_iplist DNS "Servidores DNS" "$DNS" ;;
      7) ask_domain DOMAIN "Dominio" "$DOMAIN"; ask_search SEARCH "Dominios search" "$SEARCH" ;;
      8) read -rp "Interfaz [$IFACE]: " a; IFACE=${a:-$IFACE}
         ask_ip SERVER_IP "IP del servidor" "$SERVER_IP" ;;
      9) read -rp "Nombre [$NAME]: " a; NAME=${a:-$NAME} ;;
      0) return ;;
      *) continue ;;
    esac
    save_net; ok "Cambio guardado (recuerda APLICAR la configuracion)."
  done
}
delete_net() {
  choose_net || return
  read -rp "¿Eliminar la red #$ID ($SUBNET) y sus reservas? [s/N]: " a
  if [[ ${a,,} == s* ]]; then rm -f "$DB_DIR/net_$ID.conf" "$DB_DIR/net_$ID.res"; ok "Red eliminada."; fi
}

# ------------------------------------------------------ ejemplo del diagrama
add_example_net() {  # name subnet rs re excl lease gw dns domain serverip resname resip
  clear_net; ID=$(next_id)
  NAME=$1; SUBNET=$2; RS=$3; RE=$4; EXCL=$5; LEASE=$6; GW=$7; DNS=$8; DOMAIN=$9; SERVER_IP=${10}
  local hint=$SERVER_IP
  detect_iface || { IFACE=""; SERVER_IP=$hint; }
  if [[ -z $IFACE ]]; then
    warn "No se detecta interfaz para $SUBNET (IP prevista del servidor: $hint)."
    read -rp "  Nombre de interfaz para esta red [eth0]: " a; IFACE=${a:-eth0}
  fi
  save_net
  local mac
  read -rp "  MAC de ${11} (reserva ${12}) [ENTER = omitir]: " mac
  if [[ -n $mac ]] && valid_mac "$mac"; then echo "$(norm_mac "$mac")|${12}|${11}" >> "$(res_file)"; fi
  ok "Red '$NAME' creada con id $ID."
}
load_example() {
  echo "${B}== Ejemplo del diagrama (servidor DebianT con 2 redes) ==${N}"
  warn "La imagen indica 172.26.299.0/24 (invalida); se usa 172.26.229.0/24."
  add_example_net "dhcpuno.org" "150.160.229.0/24" "150.160.229.180" "150.160.229.190" "pares" 3600 \
    "150.160.229.201" "1.2.3.4, 5.6.7.8" "dhcpuno.org" "150.160.229.200" "UbuntuG" "150.160.229.101"
  add_example_net "dhcpdos.net" "172.26.229.0/24" "172.26.229.50" "172.26.229.60" "impares" 7200 \
    "172.26.229.101" "4.3.2.1, 8.7.6.5" "dhcpdos.net" "172.26.229.200" "Win10" "172.26.229.100"
}

# ----------------------------------------------------------- aplicar / estado
kea_service() {
  local s
  for s in kea-dhcp4-server kea-dhcp4; do
    systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "^$s.service" && { echo "$s"; return; }
  done
  return 1
}
apply_conf() {
  local auto=$1 tmp out rc svc owner
  if [[ -z $(list_ids) ]]; then err "No hay redes definidas."; return 1; fi
  tmp=$(mktemp) || return 1
  gen_json > "$tmp"
  if command -v kea-dhcp4 >/dev/null; then
    out=$(kea-dhcp4 -t "$tmp" 2>&1); rc=$?
    if (( rc != 0 )); then err "KEA rechaza la configuracion:"; echo "$out"; rm -f "$tmp"; return 1; fi
    ok "Sintaxis validada con kea-dhcp4 -t"
  else
    warn "kea-dhcp4 no esta instalado: no se puede validar."
    if [[ $auto != auto ]]; then read -rp "¿Escribir el fichero igualmente? [s/N]: " a; [[ ${a,,} == s* ]] || { rm -f "$tmp"; return 1; }; fi
  fi
  mkdir -p "$(dirname "$CONF")" 2>/dev/null
  if [[ -f $CONF ]]; then
    cp -p "$CONF" "$CONF.bak-$(date +%Y%m%d-%H%M%S)" && info "Copia de seguridad creada en $CONF.bak-*"
  fi
  if ! cp "$tmp" "$CONF"; then err "No puedo escribir $CONF (¿eres root?)"; rm -f "$tmp"; return 1; fi
  rm -f "$tmp"
  chmod 640 "$CONF"
  for owner in _kea kea; do id "$owner" &>/dev/null && { chown "$owner:$owner" "$CONF" 2>/dev/null; break; }; done
  ok "Configuracion escrita en $CONF"
  if svc=$(kea_service); then
    systemctl enable "$svc" >/dev/null 2>&1
    if systemctl restart "$svc"; then ok "Servicio $svc reiniciado."; else
      err "Fallo al reiniciar. Revisa: journalctl -u $svc --no-pager -n 50"; return 1; fi
    sleep 1
    systemctl --no-pager -l status "$svc" 2>/dev/null | head -n 5
    echo "Puerto UDP 67:"; ss -lun 2>/dev/null | grep -E ':67\b' || warn "UDP/67 no aparece abierto."
  else
    warn "No se encontro el servicio de KEA (kea-dhcp4-server). Reinicialo manualmente."
  fi
}
show_leases() {
  if [[ ! -f $LEASES_FILE ]]; then warn "No existe $LEASES_FILE"; return; fi
  printf '%-16s %-18s %-20s %-7s %s\n' IP MAC CADUCA SUBRED HOSTNAME
  tail -n +2 "$LEASES_FILE" | awk -F, '{l[$1]=$0} END{for(k in l) print l[k]}' | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n |
  while IFS=, read -r ip mac _ _ exp sub _ _ host st _; do
    [[ $st == 0 ]] || continue
    printf '%-16s %-18s %-20s %-7s %s\n' "$ip" "$mac" "$(date -d "@$exp" '+%F %T' 2>/dev/null)" "$sub" "$host"
  done
}
install_offline() {
  read -rp "Carpeta con los .deb de KEA (kea-dhcp4-server, libkea*, kea-common...): " d
  ls "$d"/*.deb >/dev/null 2>&1 || { err "No hay .deb en $d"; return; }
  dpkg -i "$d"/*.deb && ok "Instalado." || err "Fallo al instalar (faltan dependencias en la carpeta?)"
}

# ----------------------------------------------------------------------- menu
usage() { sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//'; }
main_menu() {
  local c
  while true; do
    echo
    echo "${B}=========== GESTOR KEA DHCP4 (offline) ===========${N}"
    echo " Redes definidas: $(list_ids | wc -l)      Fichero: $CONF"
    echo "  1) Listar redes"
    echo "  2) Anadir red"
    echo "  3) Modificar red (rango, exclusiones, reservas...)"
    echo "  4) Eliminar red"
    echo "  5) Ver JSON generado (sin aplicar)"
    echo "  6) APLICAR (validar, copia de seguridad, escribir, reiniciar)"
    echo "  7) Ver concesiones activas"
    echo "  8) Cargar ejemplo del diagrama (DebianT, 2 redes)"
    echo "  9) Instalar KEA desde .deb locales"
    echo "  0) Salir"
    read -rp "> " c
    case $c in
      1) list_nets; pause ;;
      2) new_net; pause ;;
      3) edit_net ;;
      4) delete_net ;;
      5) gen_json | ${PAGER:-cat}; pause ;;
      6) apply_conf; pause ;;
      7) show_leases; pause ;;
      8) load_example; pause ;;
      9) install_offline; pause ;;
      0) exit 0 ;;
    esac
  done
}

init_db
case "${1:-}" in
  -g|--generate) gen_json ;;
  -a|--apply)    apply_conf auto ;;
  -h|--help)     usage ;;
  "")            main_menu ;;
  *)             usage; exit 1 ;;
esac
__EOF_KEA_MANAGER__
}


# ---------------------------------------------------------
# Programa principal
# ---------------------------------------------------------

case "${1:-}" in
    -h|--help|help)
        usage
        exit 0
        ;;
    --extraer)
        extract_all "${2:-.}"
        exit $?
        ;;
esac

detect_backend

# "estado" se puede consultar sin root; el resto lo necesita
if [ "${1:-}" = "estado" ]; then
    show_status
    exit 0
fi

if [ "$EUID" -ne 0 ]; then
    echo "ERROR: Este script debe ejecutarse como root."
    echo "Ejecutalo con: sudo $0"
    exit 1
fi

case "${1:-}" in
    "")       main_menu ;;
    red)      run_network ;;
    kea)      run_script kea ;;
    completo) wizard ;;
    *)        usage; exit 1 ;;
esac
