#!/bin/bash

# ============================================================
# TROCA AUTOMÁTICA DE IP - NETPLAN
# Ubuntu 18.04 / 20.04
# ============================================================

set -u

NETPLAN_DIR="/etc/netplan"

echo
echo "=============================================="
echo "       TROCA AUTOMÁTICA DE IP"
echo "       Imperio Apps"
echo "=============================================="
echo

# ============================================================
# ROOT
# ============================================================

if [ "$EUID" -ne 0 ]; then
    echo "ERRO: execute como root."
    exit 1
fi

# ============================================================
# DEPENDÊNCIAS
# ============================================================

if ! command -v ip >/dev/null 2>&1; then
    echo "ERRO: comando 'ip' não encontrado."
    exit 1
fi

if ! command -v netplan >/dev/null 2>&1; then
    echo "ERRO: Netplan não encontrado."
    exit 1
fi

if ! command -v python3 >/dev/null 2>&1; then
    echo "ERRO: Python 3 não encontrado."
    exit 1
fi

# ============================================================
# DETECTAR INTERFACE DA ROTA DEFAULT
# ============================================================

INTERFACE=$(ip route show default 2>/dev/null | awk 'NR==1 {print $5}')

if [ -z "$INTERFACE" ]; then
    echo "ERRO: não foi possível detectar a interface principal."
    exit 1
fi

if [ ! -d "/sys/class/net/$INTERFACE" ]; then
    echo "ERRO: interface $INTERFACE não existe."
    exit 1
fi

# ============================================================
# DETECTAR MAC
# ============================================================

MAC=$(cat "/sys/class/net/$INTERFACE/address" 2>/dev/null || true)

if [ -z "$MAC" ]; then
    echo "ERRO: não foi possível detectar o MAC."
    exit 1
fi

MAC=$(echo "$MAC" | tr '[:upper:]' '[:lower:]')

echo "Interface detectada: $INTERFACE"
echo "MAC detectado      : $MAC"
echo

# ============================================================
# LOCALIZAR ARQUIVO NETPLAN
# ============================================================

NETPLAN_FILE=""

for FILE in "$NETPLAN_DIR"/*.yaml "$NETPLAN_DIR"/*.yml; do

    [ -f "$FILE" ] || continue
    [ -s "$FILE" ] || continue

    # Ignorar backups
    case "$FILE" in
        *.bak.*)
            continue
            ;;
    esac

    # Precisa conter network:
    grep -qE '^[[:space:]]*network:' "$FILE" 2>/dev/null || continue

    # Procurar diretamente pela interface
    if grep -Eq \
        "^[[:space:]]*${INTERFACE}:[[:space:]]*$" \
        "$FILE" 2>/dev/null; then

        NETPLAN_FILE="$FILE"
        break
    fi

    # Procurar pelo MAC
    if grep -qi "$MAC" "$FILE" 2>/dev/null; then
        NETPLAN_FILE="$FILE"
        break
    fi

done

# ============================================================
# VALIDAR ARQUIVO
# ============================================================

if [ -z "$NETPLAN_FILE" ]; then

    echo
    echo "ERRO: nenhum arquivo Netplan contendo"
    echo "a interface $INTERFACE foi encontrado."
    echo

    echo "Arquivos disponíveis:"
    ls -lah "$NETPLAN_DIR"

    exit 1
fi

echo
echo "Arquivo Netplan selecionado:"
echo "$NETPLAN_FILE"
echo

# ============================================================
# CONFIGURAÇÃO ATUAL
# ============================================================

OLD_IP=$(ip -4 addr show "$INTERFACE" 2>/dev/null |
    awk '/inet / {print $2; exit}')

OLD_GW=$(ip route show default 2>/dev/null |
    awk 'NR==1 {print $3}')

echo "----------------------------------------------"
echo "CONFIGURAÇÃO ATUAL"
echo "----------------------------------------------"
echo "Interface : $INTERFACE"
echo "MAC       : $MAC"
echo "IP        : ${OLD_IP:-não detectado}"
echo "Gateway   : ${OLD_GW:-não detectado}"
echo "----------------------------------------------"
echo

# ============================================================
# NOVOS DADOS
# ============================================================

read -rp "Digite o NOVO IP: " NEW_IP < /dev/tty
read -rp "Digite a MÁSCARA (ex: 255.255.255.248): " NEW_MASK < /dev/tty
read -rp "Digite o NOVO GATEWAY: " NEW_GW < /dev/tty

if [ -z "$NEW_IP" ] || [ -z "$NEW_MASK" ] || [ -z "$NEW_GW" ]; then
    echo
    echo "ERRO: todos os campos são obrigatórios."
    exit 1
fi

# ============================================================
# VALIDAR IPv4
# ============================================================

validate_ipv4() {

    local IP="$1"
    local IFS=.
    local OCTETS

    read -r -a OCTETS <<< "$IP"

    [ "${#OCTETS[@]}" -eq 4 ] || return 1

    for OCTET in "${OCTETS[@]}"; do

        [[ "$OCTET" =~ ^[0-9]+$ ]] || return 1

        [ "$OCTET" -ge 0 ] 2>/dev/null || return 1
        [ "$OCTET" -le 255 ] 2>/dev/null || return 1

    done

    return 0
}

if ! validate_ipv4 "$NEW_IP"; then
    echo
    echo "ERRO: novo IP inválido: $NEW_IP"
    exit 1
fi

if ! validate_ipv4 "$NEW_GW"; then
    echo
    echo "ERRO: gateway inválido: $NEW_GW"
    exit 1
fi

# ============================================================
# MÁSCARA -> PREFIXO
# ============================================================

mask_to_prefix() {

    case "$1" in
        255.255.255.255) echo 32 ;;
        255.255.255.254) echo 31 ;;
        255.255.255.252) echo 30 ;;
        255.255.255.248) echo 29 ;;
        255.255.255.240) echo 28 ;;
        255.255.255.224) echo 27 ;;
        255.255.255.192) echo 26 ;;
        255.255.255.128) echo 25 ;;
        255.255.255.0)   echo 24 ;;
        255.255.254.0)   echo 23 ;;
        255.255.252.0)   echo 22 ;;
        255.255.248.0)   echo 21 ;;
        255.255.240.0)   echo 20 ;;
        255.255.224.0)   echo 19 ;;
        255.255.192.0)   echo 18 ;;
        255.255.128.0)   echo 17 ;;
        255.255.0.0)     echo 16 ;;
        255.254.0.0)     echo 15 ;;
        255.252.0.0)     echo 14 ;;
        255.248.0.0)     echo 13 ;;
        255.240.0.0)     echo 12 ;;
        255.224.0.0)     echo 11 ;;
        255.192.0.0)     echo 10 ;;
        255.128.0.0)     echo 9 ;;
        255.0.0.0)       echo 8 ;;
        *) return 1 ;;
    esac
}

NEW_PREFIX=$(mask_to_prefix "$NEW_MASK") || {

    echo
    echo "ERRO: máscara inválida."
    echo "Exemplo: 255.255.255.240"
    exit 1
}

# ============================================================
# CONFIRMAÇÃO
# ============================================================

echo
echo "=============================================="
echo "NOVA CONFIGURAÇÃO"
echo "=============================================="
echo "Arquivo   : $NETPLAN_FILE"
echo "Interface : $INTERFACE"
echo "IP        : $NEW_IP"
echo "Máscara   : $NEW_MASK"
echo "Prefixo   : /$NEW_PREFIX"
echo "Gateway   : $NEW_GW"
echo "=============================================="
echo

read -rp "Confirma a alteração? [s/N]: " CONFIRM < /dev/tty

case "$CONFIRM" in
    s|S|sim|SIM|Sim)
        ;;
    *)
        echo
        echo "Operação cancelada."
        exit 0
        ;;
esac

# ============================================================
# BACKUP
# ============================================================

BACKUP="${NETPLAN_FILE}.bak.$(date +%Y%m%d-%H%M%S)"

echo
echo "[1/5] Criando backup..."

if ! cp -a "$NETPLAN_FILE" "$BACKUP"; then
    echo "ERRO ao criar backup."
    exit 1
fi

echo "Backup: $BACKUP"

# ============================================================
# ALTERAR YAML
# ============================================================

echo
echo "[2/5] Alterando configuração..."

python3 - "$NETPLAN_FILE" "$INTERFACE" "$MAC" "$NEW_IP" "$NEW_PREFIX" "$NEW_GW" <<'PY'

import sys
import re

file = sys.argv[1]
interface = sys.argv[2]
mac = sys.argv[3].lower()
new_ip = sys.argv[4]
prefix = sys.argv[5]
new_gw = sys.argv[6]

# ============================================================
# LER ARQUIVO
# ============================================================

with open(file, "r") as f:
    lines = f.readlines()

# ============================================================
# LOCALIZAR BLOCO DA INTERFACE
# ============================================================

start = None

interface_pattern = re.compile(
    r'^(\s*)' + re.escape(interface) + r'\s*:\s*$'
)

# 1. Procurar pelo nome da interface
for i, line in enumerate(lines):

    if interface_pattern.match(line.rstrip('\n')):
        start = i
        break

# 2. Fallback pelo MAC
if start is None:

    for i, line in enumerate(lines):

        if mac in line.lower():

            mac_indent = len(line) - len(line.lstrip())

            for j in range(i - 1, -1, -1):

                candidate = lines[j].rstrip('\n')

                if not candidate.strip():
                    continue

                indent = (
                    len(candidate) -
                    len(candidate.lstrip())
                )

                if indent >= mac_indent:
                    continue

                stripped = candidate.strip()

                if stripped == interface + ":":
                    start = j
                    break

            if start is not None:
                break

# Não encontrou
if start is None:

    print("ERRO: bloco da interface não encontrado.")
    sys.exit(1)

# ============================================================
# INDENTAÇÃO DA INTERFACE
# ============================================================

interface_line = lines[start].rstrip('\n')

interface_indent = (
    len(interface_line) -
    len(interface_line.lstrip())
)

# ============================================================
# ENCONTRAR FIM DO BLOCO
# ============================================================

end = len(lines)

for i in range(start + 1, len(lines)):

    line = lines[i].rstrip('\n')

    if not line.strip():
        continue

    indent = len(line) - len(line.lstrip())

    if indent <= interface_indent:
        end = i
        break

block = lines[start:end]

# ============================================================
# ALTERAR ADDRESSES
# ============================================================

address_found = False

for i, line in enumerate(block):

    stripped = line.strip()

    if stripped != "addresses:":
        continue

    addresses_indent = (
        len(line) -
        len(line.lstrip())
    )

    # Procurar os itens abaixo de addresses:
    for j in range(i + 1, len(block)):

        item = block[j].rstrip('\n')

        if not item.strip():
            continue

        item_indent = (
            len(item) -
            len(item.lstrip())
        )

        item_stripped = item.strip()

        # Item da lista:
        # - 38.190.177.199/28
        if item_indent > addresses_indent:

            if item_stripped.startswith("-"):

                block[j] = (
                    " " * (addresses_indent + 2)
                    + "- "
                    + new_ip
                    + "/"
                    + prefix
                    + "\n"
                )

                address_found = True
                break

        # Terminou o bloco addresses
        if item_indent <= addresses_indent:
            break

    if address_found:
        break

# ============================================================
# FALLBACK:
# SE NÃO ENCONTROU addresses, PROCURAR IP EXISTENTE
# ============================================================

if not address_found:

    ip_pattern = re.compile(
        r'\b\d{1,3}(?:\.\d{1,3}){3}/\d{1,2}\b'
    )

    for i, line in enumerate(block):

        if ip_pattern.search(line):

            indent = (
                len(line) -
                len(line.lstrip())
            )

            # Se for uma linha de lista
            if line.lstrip().startswith("-"):

                block[i] = (
                    " " * indent
                    + "- "
                    + new_ip
                    + "/"
                    + prefix
                    + "\n"
                )

            else:

                block[i] = (
                    " " * indent
                    + new_ip
                    + "/"
                    + prefix
                    + "\n"
                )

            address_found = True
            break

# ============================================================
# ALTERAR gateway4
# ============================================================

gateway_found = False

for i, line in enumerate(block):

    if re.match(
        r'^\s*gateway4\s*:',
        line
    ):

        indent = (
            len(line) -
            len(line.lstrip())
        )

        block[i] = (
            " " * indent
            + "gateway4: "
            + new_gw
            + "\n"
        )

        gateway_found = True
        break

# ============================================================
# FALLBACK:
# ROUTES -> VIA
# ============================================================

if not gateway_found:

    for i, line in enumerate(block):

        if re.search(
            r'\bto\s*:\s*0\.0\.0\.0/0',
            line
        ):

            for j in range(
                i + 1,
                min(i + 10, len(block))
            ):

                if re.match(
                    r'^\s*via\s*:',
                    block[j]
                ):

                    indent = (
                        len(block[j]) -
                        len(block[j].lstrip())
                    )

                    block[j] = (
                        " " * indent
                        + "via: "
                        + new_gw
                        + "\n"
                    )

                    gateway_found = True
                    break

            if gateway_found:
                break

# ============================================================
# VALIDAR RESULTADO
# ============================================================

if not address_found:

    print("ERRO: endereço IP não encontrado.")
    sys.exit(1)

if not gateway_found:

    print("ERRO: gateway não encontrado.")
    sys.exit(1)

# ============================================================
# SALVAR
# ============================================================

lines[start:end] = block

with open(file, "w") as f:
    f.writelines(lines)

print("Configuração alterada com sucesso.")

PY

# ============================================================
# VERIFICAR ALTERAÇÃO DO SCRIPT
# ============================================================

if [ $? -ne 0 ]; then

    echo
    echo "ERRO ao alterar configuração."
    echo "Restaurando backup..."

    cp -a "$BACKUP" "$NETPLAN_FILE"

    exit 1
fi

# ============================================================
# MOSTRAR CONFIGURAÇÃO ALTERADA
# ============================================================

echo
echo "Configuração resultante:"
echo "----------------------------------------------"

awk -v iface="$INTERFACE" '
{
    line=$0

    if (line ~ "^[[:space:]]*" iface ":[[:space:]]*$") {
        found=1
        print
        next
    }

    if (found) {
        if (line ~ /^[^[:space:]]/) {
            exit
        }

        print
    }
}
' "$NETPLAN_FILE" | head -40

echo "----------------------------------------------"

# ============================================================
# VALIDAR NETPLAN
# ============================================================

echo
echo "[3/5] Validando Netplan..."

if ! netplan generate; then

    echo
    echo "ERRO: Netplan inválido."
    echo "Restaurando backup..."

    cp -a "$BACKUP" "$NETPLAN_FILE"

    netplan generate >/dev/null 2>&1 || true

    echo "Backup restaurado."

    exit 1
fi

echo "Netplan válido."

# ============================================================
# APLICAR
# ============================================================

echo
echo "[4/5] Aplicando nova configuração..."
echo
echo "ATENÇÃO: a conexão SSH pode cair."
echo

sleep 3

# ============================================================
# APLICAR CONFIGURAÇÃO
# ============================================================

# netplan try existe nas versões utilizadas.
# Se falhar, restauramos o backup.

if netplan try --timeout 20; then

    echo
    echo "Nova configuração aceita."

else

    echo
    echo "ERRO: não foi possível aplicar a nova configuração."
    echo "Restaurando backup..."

    cp -a "$BACKUP" "$NETPLAN_FILE"

    netplan generate >/dev/null 2>&1 || true
    netplan apply >/dev/null 2>&1 || true

    echo
    echo "Rollback realizado."

    exit 1
fi

sleep 5

# ============================================================
# VERIFICAR CONFIGURAÇÃO
# ============================================================

echo
echo "[5/5] Verificando configuração..."

CURRENT_IP=$(
    ip -4 addr show "$INTERFACE" 2>/dev/null |
    awk '/inet / {print $2}' |
    grep -Fx "${NEW_IP}/${NEW_PREFIX}" || true
)

CURRENT_GW=$(
    ip route show default 2>/dev/null |
    awk 'NR==1 {print $3}'
)

# ============================================================
# VALIDAR IP
# ============================================================

if [ -z "$CURRENT_IP" ]; then

    echo
    echo "ERRO: o novo IP não foi aplicado."
    echo
    echo "Restaurando backup..."

    cp -a "$BACKUP" "$NETPLAN_FILE"

    netplan generate >/dev/null 2>&1 || true
    netplan apply >/dev/null 2>&1 || true

    echo
    echo "Rollback realizado."

    exit 1
fi

# ============================================================
# RESULTADO
# ============================================================

echo
echo "=============================================="
echo "       ALTERAÇÃO CONCLUÍDA"
echo "=============================================="
echo
echo "Arquivo   : $NETPLAN_FILE"
echo "Interface : $INTERFACE"
echo "IP        : $CURRENT_IP"
echo "Gateway   : $CURRENT_GW"
echo "Backup    : $BACKUP"
echo
echo "=============================================="
echo
