#!/bin/sh
# RNRemote - Instalador do Agente pfSense
# Execute no pfSense como root:
#   curl -fsSL https://rnremote.joaoneto.tec.br/static/agent/install-pfsense.sh | sh

PANEL_URL="https://rnremote.joaoneto.tec.br"
RELAY_URL="wss://rnremote.joaoneto.tec.br/ws"
AGENT_DIR="/usr/local/rnremote"
AGENT_BIN="${AGENT_DIR}/agent.py"
CONFIG_DIR="/usr/local/etc/rnremote"
CONFIG_FILE="${CONFIG_DIR}/agent.json"
RC_SCRIPT="/usr/local/etc/rc.d/rnremote_agent"

ok()   { printf "  [OK] %s\n" "$1"; }
err()  { printf "  [X]  %s\n" "$1" >&2; exit 1; }
info() { printf "  ->   %s\n" "$1"; }
warn() { printf "  [!]  %s\n" "$1"; }
step() { printf "\n[%s] %s\n" "$1" "$2"; }

printf "\n"
printf "  ╔══════════════════════════════════════════╗\n"
printf "  ║   RNRemote — Instalador do Agente        ║\n"
printf "  ║   pfSense / FreeBSD                      ║\n"
printf "  ╚══════════════════════════════════════════╝\n\n"

# ── Root ─────────────────────────────────────────────────────────────────────
[ "$(id -u)" -ne 0 ] && err "Execute como root."
uname -s | grep -q FreeBSD || err "Este script é exclusivo para pfSense/FreeBSD."

# ── Passo 1: Python ──────────────────────────────────────────────────────────
step "1/5" "Verificando Python 3..."

PYTHON=""
for cmd in python3 python3.11 python3.10 python3.9 python3.8; do
    if command -v "$cmd" >/dev/null 2>&1; then
        PYTHON="$cmd"
        break
    fi
done

if [ -z "$PYTHON" ]; then
    info "Instalando Python..."
    for pkg in python311 python310 python39 python38; do
        pkg install -y "$pkg" 2>/dev/null && break
    done
    for cmd in python3 python3.11 python3.10 python3.9; do
        command -v "$cmd" >/dev/null 2>&1 && PYTHON="$cmd" && break
    done
fi

[ -z "$PYTHON" ] && err "Não foi possível instalar Python3. Tente: pkg install python311"
ok "Python: $PYTHON ($($PYTHON --version 2>&1))"

# Cria symlink python3 se não existir (necessário para o serviço)
if ! command -v python3 >/dev/null 2>&1; then
    PYABS=$(command -v "$PYTHON")
    ln -sf "$PYABS" /usr/local/bin/python3
    ok "Symlink: python3 -> $PYABS"
fi

# ── Passo 2: Dependências ────────────────────────────────────────────────────
step "2/5" "Instalando dependências Python..."

PYVER=$($PYTHON -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')" 2>/dev/null || echo "311")

# Verifica se websockets já está disponível
if $PYTHON -c "import websockets" 2>/dev/null; then
    ok "Dependências OK (websockets já instalado)"
else
    info "Instalando websockets..."

    # Tentativa 1: pkg (FreeBSD nativo)
    pkg install -y "py${PYVER}-websockets" 2>/dev/null && \
        $PYTHON -c "import websockets" 2>/dev/null && ok "Dependências OK (pkg)" || true

    # Tentativa 2: pip com --break-system-packages (Python 3.11+ PEP 668) ignorando SSL
    if ! $PYTHON -c "import websockets" 2>/dev/null; then
        PIP=""
        PYVER_MAJ=$(echo "$PYVER" | cut -c1)
        PYVER_MIN=$(echo "$PYVER" | cut -c2-)
        for cmd in pip3 "pip${PYVER_MAJ}.${PYVER_MIN}" pip; do
            command -v "$cmd" >/dev/null 2>&1 && PIP="$cmd" && break
        done
        [ -z "$PIP" ] && $PYTHON -m ensurepip --upgrade 2>/dev/null && PIP="$PYTHON -m pip"
        [ -z "$PIP" ] && PIP="$PYTHON -m pip"

        $PIP install --quiet --break-system-packages \
            --trusted-host pypi.org --trusted-host files.pythonhosted.org \
            websockets 2>/dev/null || \
        $PIP install --quiet --trusted-host pypi.org --trusted-host files.pythonhosted.org \
            websockets 2>/dev/null || true
    fi

    # Tentativa 3: download manual do wheel via curl (sem pip, sem pkg)
    if ! $PYTHON -c "import websockets" 2>/dev/null; then
        info "Tentando download manual do wheel..."
        SITE=$($PYTHON -c "import site; print(site.getsitepackages()[0])" 2>/dev/null || echo "/usr/local/lib/python3.11/site-packages")
        # websockets 12.x — wheel universal (pure Python)
        WHL_URL="https://files.pythonhosted.org/packages/py3/w/websockets/websockets-12.0-py3-none-any.whl"
        WHL_TMP="/tmp/websockets.whl"
        curl -fsSL --max-time 30 "$WHL_URL" -o "$WHL_TMP" 2>/dev/null && \
            $PYTHON -c "import zipfile,sys; zipfile.ZipFile('$WHL_TMP').extractall('$SITE')" 2>/dev/null && \
            rm -f "$WHL_TMP" || true
    fi

    $PYTHON -c "import websockets" 2>/dev/null || \
        err "Não foi possível instalar websockets. Execute manualmente: pkg install py${PYVER}-websockets"
    ok "Dependências OK"
fi

# ── Passo 3: Configuração (login no painel) ──────────────────────────────────
step "3/5" "Configurando o agente..."

NEED_CONFIG=1
if [ -f "$CONFIG_FILE" ]; then
    printf "  Configuração existente encontrada. Reconfigurar? [s/N]: "
    read -r RESP </dev/tty
    case "$RESP" in
        [sSyY]*) NEED_CONFIG=1 ;;
        *)       NEED_CONFIG=0; ok "Configuração mantida." ;;
    esac
fi

if [ "$NEED_CONFIG" -eq 1 ]; then
    printf "\n  Informe suas credenciais do painel RNRemote:\n\n"

    # E-mail
    printf "  E-mail: "
    read -r EMAIL </dev/tty

    # Senha do painel
    printf "  Senha do painel: "
    stty -echo </dev/tty 2>/dev/null
    read -r PANEL_PASS </dev/tty
    stty echo </dev/tty 2>/dev/null
    printf "\n"

    # Login na API
    LOGIN_RESP=$(curl -fsSL --max-time 15 -X POST \
        -H "Content-Type: application/json" \
        -d "{\"email\":\"${EMAIL}\",\"password\":\"${PANEL_PASS}\"}" \
        "${PANEL_URL}/api/login" 2>/dev/null) || err "Falha ao conectar ao painel."

    TOKEN=$($PYTHON -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('token',''))" <<EOF
$LOGIN_RESP
EOF
)
    LOGIN_OK=$($PYTHON -c "import sys,json; d=json.loads(sys.stdin.read()); print('ok' if d.get('ok') else d.get('error','erro'))" <<EOF
$LOGIN_RESP
EOF
)

    if [ "$LOGIN_OK" != "ok" ]; then
        err "Login inválido: $LOGIN_OK"
    fi
    ok "Autenticado no painel"

    # Apelido da máquina
    DEFAULT_NICK=$(hostname)
    printf "\n  Apelido para esta máquina [%s]: " "$DEFAULT_NICK"
    read -r NICK </dev/tty
    [ -z "$NICK" ] && NICK="$DEFAULT_NICK"

    # Senha de acesso remoto
    printf "\n  Defina a senha de acesso remoto a esta máquina:\n"
    ACCESS_PASS=""
    while [ -z "$ACCESS_PASS" ]; do
        printf "  Senha de acesso: "
        stty -echo </dev/tty 2>/dev/null
        read -r P1 </dev/tty
        stty echo </dev/tty 2>/dev/null
        printf "\n  Confirme: "
        stty -echo </dev/tty 2>/dev/null
        read -r P2 </dev/tty
        stty echo </dev/tty 2>/dev/null
        printf "\n"
        if [ -z "$P1" ]; then
            warn "Senha não pode ser vazia."
        elif [ "$P1" != "$P2" ]; then
            warn "Senhas não coincidem. Tente novamente."
        else
            ACCESS_PASS="$P1"
        fi
    done

    # Provisionar agente no painel
    # Escapa aspas simples no nick para JSON seguro
    NICK_ESC=$(printf '%s' "$NICK" | sed "s/'/\\\\'/g")
    PASS_ESC=$(printf '%s' "$ACCESS_PASS" | sed 's/"/\\"/g')

    PROV_RESP=$(curl -sSL --max-time 15 -X POST \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer ${TOKEN}" \
        -d "{\"nickname\":\"${NICK_ESC}\",\"access_password\":\"${PASS_ESC}\"}" \
        "${PANEL_URL}/api/agents/provision" 2>/dev/null) || err "Falha ao conectar ao painel para registrar agente."

    AGENT_ID=$($PYTHON -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('agent_id',''))" <<EOF
$PROV_RESP
EOF
)
    BINDING_SECRET=$($PYTHON -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('binding_secret',''))" <<EOF
$PROV_RESP
EOF
)
    PROV_OK=$($PYTHON -c "import sys,json; d=json.loads(sys.stdin.read()); print('ok' if d.get('ok') else d.get('error','erro'))" <<EOF
$PROV_RESP
EOF
)

    if [ "$PROV_OK" != "ok" ] || [ -z "$AGENT_ID" ]; then
        err "Registro falhou: ${PROV_OK}. Verifique se o usuário '${EMAIL}' tem permissão 'Agentes - Criar' no painel."
    fi
    ok "Agente registrado — ID: $AGENT_ID"

    # Salva config
    mkdir -p "$CONFIG_DIR"
    cat > "$CONFIG_FILE" <<EOF
{
  "relay_url": "${RELAY_URL}",
  "agent_id": "${AGENT_ID}",
  "password": "${ACCESS_PASS}",
  "binding_secret": "${BINDING_SECRET}",
  "nickname": "${NICK}"
}
EOF
    chmod 600 "$CONFIG_FILE"
    ok "Config salva em $CONFIG_FILE"
fi

# ── Passo 4: Download do agente ──────────────────────────────────────────────
step "4/5" "Baixando agente..."

mkdir -p "$AGENT_DIR"
if [ -n "$TOKEN" ]; then
    # Nova instalação: Bearer token do login
    fetch -q -o "$AGENT_BIN" \
        --header "Authorization: Bearer ${TOKEN}" \
        "${PANEL_URL}/api/agents/download/agent-pfsense.py" 2>/dev/null || \
    $PYTHON -c "
import urllib.request
req = urllib.request.Request('${PANEL_URL}/api/agents/download/agent-pfsense.py',
    headers={'Authorization': 'Bearer ${TOKEN}'})
open('${AGENT_BIN}', 'wb').write(urllib.request.urlopen(req, timeout=30).read())
" || err "Falha ao baixar o agente."
else
    # Re-instalação com config existente: TOKEN vazio — usa X-Agent-Id + X-Binding-Token
    info "Usando config existente para autenticar download..."
    $PYTHON -c "
import json, hashlib, urllib.request
c    = json.load(open('${CONFIG_FILE}'))
aid  = c.get('agent_id', '')
bsec = c.get('binding_secret', '')
btok = hashlib.sha256((bsec + aid).encode()).hexdigest()
req  = urllib.request.Request('${PANEL_URL}/api/agents/download/agent-pfsense.py',
    headers={'X-Agent-Id': aid, 'X-Binding-Token': btok})
open('${AGENT_BIN}', 'wb').write(urllib.request.urlopen(req, timeout=30).read())
" || err "Falha ao baixar o agente. Verifique se o agente está registrado no painel."
fi

chmod 750 "$AGENT_BIN"
ok "Agente salvo em $AGENT_BIN"

# ── Passo 5: Serviço rc.d + watchdog ────────────────────────────────────────
step "5/5" "Instalando serviço e watchdog..."

PYTHON_ABS=$(command -v python3)
WATCHDOG_SCRIPT="${CONFIG_DIR}/watchdog.sh"

cat > "$RC_SCRIPT" <<RCEOF
#!/bin/sh
#
# PROVIDE: rnremote_agent
# REQUIRE: networking
# KEYWORD: shutdown

. /etc/rc.subr

name="rnremote_agent"
rcvar="rnremote_agent_enable"
pidfile="/var/run/\${name}.pid"
logfile="/var/log/rnremote.log"

# Usa /usr/sbin/daemon para daemonizar o processo Python
# -P: grava PID do daemon (não do filho) → service stop funciona com -r
# -r: reinicia filho automaticamente se morrer
command="/usr/sbin/daemon"
command_args="-r -P \${pidfile} -o \${logfile} ${PYTHON_ABS} ${AGENT_BIN} --config ${CONFIG_FILE}"

load_rc_config \$name
: \${rnremote_agent_enable:="NO"}

run_rc_command "\$1"
RCEOF

chmod 555 "$RC_SCRIPT"

# pfSense regenera /etc/rc.conf a cada boot — usar /etc/rc.conf.local que não é sobrescrito
grep -q "rnremote_agent_enable" /etc/rc.conf.local 2>/dev/null || \
    echo 'rnremote_agent_enable="YES"' >> /etc/rc.conf.local
# Garante que rc.conf.local é carregado (já é padrão no FreeBSD, mas não custa)
grep -q "rnremote_agent_enable" /etc/rc.conf 2>/dev/null || \
    echo 'rnremote_agent_enable="YES"' >> /etc/rc.conf

# ── Watchdog via cron ────────────────────────────────────────────────────────
# Garante que o agente sobe automaticamente mesmo se rc.d falhar após reboot
cat > "$WATCHDOG_SCRIPT" <<'WEOF'
#!/bin/sh
# Watchdog RNRemote: verifica se o agente está rodando e inicia se necessário
PIDFILE="/var/run/rnremote_agent.pid"
AGENT_BIN_W="/usr/local/rnremote/agent.py"
CONFIG_W="/usr/local/etc/rnremote/agent.json"
LOGFILE="/var/log/rnremote.log"

# Verifica se processo está vivo
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
    exit 0  # rodando, ok
fi

# Garante rc.conf.local habilitado
grep -q "rnremote_agent_enable" /etc/rc.conf.local 2>/dev/null || \
    echo 'rnremote_agent_enable="YES"' >> /etc/rc.conf.local

# Tenta via rc.d primeiro
service rnremote_agent start 2>/dev/null
sleep 2

# Se ainda não subiu, inicia diretamente com daemon
if ! ([ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null); then
    PYTHON3=$(command -v python3 2>/dev/null || echo /usr/local/bin/python3)
    /usr/sbin/daemon -r -p "$PIDFILE" -o "$LOGFILE" "$PYTHON3" "$AGENT_BIN_W" --config "$CONFIG_W"
fi
WEOF
chmod 755 "$WATCHDOG_SCRIPT"
ok "Watchdog: $WATCHDOG_SCRIPT"

# Instala no cron do root (persiste através de reboots no pfSense)
CRON_LINE="*/5 * * * * root ${WATCHDOG_SCRIPT} 2>/dev/null"
CRONTAB_FILE="/var/cron/tabs/root"

# Cria crontab se não existir
[ -f "$CRONTAB_FILE" ] || touch "$CRONTAB_FILE"
# Remove entrada antiga e adiciona nova
grep -v "rnremote.*watchdog\|watchdog.*rnremote" "$CRONTAB_FILE" > /tmp/rnremote_crontab_tmp 2>/dev/null || true
echo "$CRON_LINE" >> /tmp/rnremote_crontab_tmp
crontab /tmp/rnremote_crontab_tmp 2>/dev/null || cp /tmp/rnremote_crontab_tmp "$CRONTAB_FILE"
rm -f /tmp/rnremote_crontab_tmp
ok "Cron watchdog instalado (verifica a cada 5 minutos)"

# ── Inicia o serviço ─────────────────────────────────────────────────────────
# Para instância anterior se existir
service rnremote_agent stop 2>/dev/null || true
# Mata qualquer processo restante
pkill -f "${AGENT_BIN}" 2>/dev/null || true
sleep 1

service rnremote_agent start
sleep 2

# Verifica se está rodando
if [ -f "/var/run/rnremote_agent.pid" ] && kill -0 "$(cat /var/run/rnremote_agent.pid 2>/dev/null)" 2>/dev/null; then
    ok "Serviço rodando (PID $(cat /var/run/rnremote_agent.pid))"
else
    warn "Serviço não iniciou via rc.d. Iniciando diretamente..."
    /usr/sbin/daemon -r -p /var/run/rnremote_agent.pid -o /var/log/rnremote.log \
        "$PYTHON_ABS" "$AGENT_BIN" --config "$CONFIG_FILE"
    sleep 1
    if [ -f "/var/run/rnremote_agent.pid" ] && kill -0 "$(cat /var/run/rnremote_agent.pid 2>/dev/null)" 2>/dev/null; then
        ok "Agente iniciado (PID $(cat /var/run/rnremote_agent.pid))"
    else
        warn "Verifique os logs: tail -f /var/log/rnremote.log"
    fi
fi

printf "\n"
printf "  ╔══════════════════════════════════════════╗\n"
printf "  ║      Instalação concluída com sucesso!   ║\n"
printf "  ╚══════════════════════════════════════════╝\n\n"
printf "  Agente  : %s\n" "$AGENT_BIN"
printf "  Config  : %s\n" "$CONFIG_FILE"
printf "  Watchdog: verifica agente a cada 5 min via cron\n"
printf "  Serviço : service rnremote_agent {start|stop|restart|status}\n"
printf "  Logs    : tail -f /var/log/rnremote.log\n\n"
