#!/usr/bin/env bash
# Instalador do instaflowz. Roda como root numa VPS Ubuntu/Debian (ou Alma/Rocky).
#
#   curl -fsSL https://instalar.instaflowz.com.br | bash
#
# Opções:
#   --nao-interativo   sem perguntas (usado pela área do aluno)
#   --sem-api          sem a central: endereço <ip>.sslip.io, sem licença (só para testes)
#   --imagem REF       usa esta imagem Docker em vez da versão publicada
#   --build PASTA      constrói a imagem a partir do código nesta pasta (testes)
#   --api URL          central diferente da padrão
#
# Pode rodar de novo quando quiser: reaproveita os segredos e os dados.
set -Eeuo pipefail

VERSION="1.0.0"
INSTALL_DIR="/opt/instaflowz"
API_URL="${INSTAFLOWZ_API_URL:-https://api.instaflowz.com.br}"
RELEASE_URL="${INSTAFLOWZ_RELEASE_URL:-https://instalar.instaflowz.com.br}"
INSTALL_TOKEN="${INSTAFLOWZ_INSTALL_TOKEN:-}"
CADDY_IMAGE="caddy:2.8.4-alpine"
# Chave pública de release (Ed25519, PEM), colada aqui literalmente: gere com
# scripts/publicar-versao.sh --gerar-chave. Sem ela, só --imagem e --build funcionam.
# Não aceite esta chave por variável de ambiente: ela é a âncora de confiança.
RELEASE_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEALsMnDigJnIjaUZIW2TlBr51oOKIXn2FlKMbHyrZeRu0=
-----END PUBLIC KEY-----"

OFFLINE=0
IMAGE_REF=""
BUILD_DIR=""

while [ $# -gt 0 ]; do
  case "$1" in
    --nao-interativo) ;; # o instalador não faz perguntas; a opção existe para a área do aluno

    --sem-api) OFFLINE=1 ;;
    --imagem) IMAGE_REF="${2:?faltou a imagem}"; shift ;;
    --build) BUILD_DIR="${2:?faltou a pasta}"; shift ;;
    --api) API_URL="${2:?faltou a URL}"; shift ;;
    *) echo "Opção desconhecida: $1" >&2; exit 2 ;;
  esac
  shift
done

SECRETS="$INSTALL_DIR/secrets"
DATA="$INSTALL_DIR/data"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

INSTANCE_ID=""
DOMAIN=""
API_PUBLIC_KEY=""
PUBLIC_IP=""
CURRENT_STEP=""
PENDING_EVENTS=()
TERMINAL=0

color() { if [ -t 1 ]; then printf '\033[%sm%s\033[0m' "$1" "$2"; else printf '%s' "$2"; fi; }
say() { printf '  %s %s\n' "$(color '32' '✓')" "$1"; }
info() { printf '  %s\n' "$1"; }

# ---------- relatórios para a central (aparecem ao vivo na área do aluno) ----------

sign() { # método caminho corpo -> imprime "timestamp assinatura"
  local ts hash
  ts="$(date +%s)"
  hash="$(printf '%s' "$3" | sha256sum | cut -d' ' -f1)"
  printf '%s\n%s\n%s\n%s' "$ts" "$1" "$2" "$hash" > "$WORK/msg"
  printf '%s %s' "$ts" "$(openssl pkeyutl -sign -inkey "$SECRETS/instance_key" -rawin -in "$WORK/msg" | base64 -w0)"
}

send_event() { # etapa status mensagem
  [ -n "$INSTANCE_ID" ] || { PENDING_EVENTS+=("$1|$2|$3"); return 0; }
  local path="/v1/instances/$INSTANCE_ID/events" body sig ts signature
  body="$(jq -nc --arg s "$1" --arg st "$2" --arg m "$3" '{step:$s,status:$st,message:$m}')"
  sig="$(sign POST "$path" "$body")"; ts="${sig%% *}"; signature="${sig#* }"
  curl -fsS -m 10 -X POST "$API_URL$path" -H 'Content-Type: application/json' \
    -H "X-Ifz-Instance: $INSTANCE_ID" -H "X-Ifz-Timestamp: $ts" -H "X-Ifz-Signature: $signature" \
    --data "$body" >/dev/null 2>&1 || true
}

flush_events() {
  local item
  for item in "${PENDING_EVENTS[@]}"; do
    IFS='|' read -r s st m <<<"$item"
    send_event "$s" "$st" "$m"
  done
  PENDING_EVENTS=()
}

step() { CURRENT_STEP="$1"; [ "$OFFLINE" = 1 ] || send_event "$1" running "$2"; }
done_step() { say "$2"; [ "$OFFLINE" = 1 ] || send_event "$1" ok "$2"; }

fail() {
  printf '\n  %s %s\n\n' "$(color '31' '✗')" "$1" >&2
  [ "$OFFLINE" = 1 ] || send_event "${CURRENT_STEP:-verificando}" error "$1"
  exit 1
}
trap 'fail "A instalação parou na etapa \"${CURRENT_STEP:-início}\". Rode o comando de novo; se repetir, mande o arquivo /var/log/instaflowz-install.log para o suporte."' ERR

# ---------- etapas ----------

check_basics() {
  step verificando "Conferindo a VPS"
  [ "$(id -u)" = 0 ] || fail "Rode como root (entre com o usuário root no SSH)."
  [ -r /etc/os-release ] || fail "Sistema operacional não reconhecido. Use Ubuntu 22.04."
  # shellcheck disable=SC1091
  . /etc/os-release
  OS_ID="$ID"; OS_LIKE="${ID_LIKE:-}"; OS_NAME="${PRETTY_NAME:-$ID}"
  case "$OS_ID $OS_LIKE" in
    *ubuntu*|*debian*) PKG=apt ;;
    *rhel*|*centos*|*fedora*|*almalinux*|*rocky*) PKG=dnf ;;
    *) fail "Sistema $OS_NAME não suportado. Reinstale a VPS com Ubuntu 22.04." ;;
  esac
  [ "$(uname -m)" = x86_64 ] || [ "$(uname -m)" = aarch64 ] || fail "Processador $(uname -m) não suportado."
  local mem_mb disk_gb
  mem_mb="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)"
  disk_gb="$(df -Pk / | awk 'NR==2 {print int($4/1024/1024)}')"
  [ "$mem_mb" -ge 900 ] || fail "A VPS tem ${mem_mb} MB de RAM. O mínimo é 1 GB."
  [ "$disk_gb" -ge 5 ] || fail "Só ${disk_gb} GB livres no disco. O mínimo é 5 GB."
  done_step verificando "$OS_NAME · $((mem_mb / 1024 + (mem_mb % 1024 > 512))) GB de RAM · $disk_gb GB livres"
}

install_packages() {
  step sistema "Instalando ferramentas básicas"
  if [ "$PKG" = apt ]; then
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -qq
    apt-get install -y -qq curl ca-certificates openssl jq iproute2 fail2ban unattended-upgrades >/dev/null
    dpkg-reconfigure -f noninteractive unattended-upgrades >/dev/null 2>&1 || true
  else
    dnf -y -q install curl ca-certificates openssl jq iproute epel-release >/dev/null
    dnf -y -q install fail2ban dnf-automatic >/dev/null 2>&1 || true
    systemctl enable --now dnf-automatic-install.timer >/dev/null 2>&1 || true
  fi
  done_step sistema "Ferramentas e atualizações de segurança automáticas"
}

random_code() {
  # 32 letras e dígitos sem I, O, 0 e 1; byte % 32 é uniforme.
  local alphabet='ABCDEFGHJKLMNPQRSTUVWXYZ23456789' out='' byte
  for byte in $(od -An -N6 -tu1 /dev/urandom); do out+="${alphabet:$((byte % 32)):1}"; done
  printf '%s-%s' "${out:0:3}" "${out:3:3}"
}

write_secret() { # nome comando
  [ -s "$SECRETS/$1" ] && return 0
  eval "$2" > "$SECRETS/$1"
}

create_secrets() {
  step segredos "Gerando as chaves desta instalação"
  umask 077
  mkdir -p "$SECRETS" "$DATA"
  write_secret master_key "openssl rand -base64 32"
  write_secret session_secret "openssl rand -base64 32"
  write_secret webhook_verify_token "openssl rand -hex 24"
  write_secret setup_token "openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n'"
  write_secret setup_code "random_code"
  [ -s "$SECRETS/instance_key" ] || openssl genpkey -algorithm ed25519 -out "$SECRETS/instance_key" 2>/dev/null
  chown -R 1000:1000 "$SECRETS" "$DATA"
  chmod 700 "$SECRETS"
  chmod 400 "$SECRETS"/*
  done_step segredos "Chaves geradas (ficam só nesta VPS)"
}

detect_ip() {
  PUBLIC_IP="$(curl -fsS4 -m 10 https://api.ipify.org 2>/dev/null || curl -fsS4 -m 10 https://ifconfig.me 2>/dev/null || true)"
  [[ "$PUBLIC_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "Não consegui descobrir o IP público da VPS. Confira a conexão com a internet."
}

register() {
  step endereco "Criando o endereço do seu sistema"
  detect_ip
  if [ "$OFFLINE" = 1 ]; then
    DOMAIN="${PUBLIC_IP//./-}.sslip.io"
    done_step endereco "Endereço de teste: $DOMAIN"
    return
  fi
  local path="/v1/register" body sig ts signature pub code response
  pub="$(openssl pkey -in "$SECRETS/instance_key" -pubout -outform DER | base64 -w0)"
  body="$(jq -nc --arg t "$INSTALL_TOKEN" --arg k "$pub" --arg v "$VERSION" '{public_key:$k, version:$v} + (if $t != "" then {install_token:$t} else {} end)')"
  sig="$(sign POST "$path" "$body")"; ts="${sig%% *}"; signature="${sig#* }"
  response="$(curl -sS -m 30 -w '\n%{http_code}' -X POST "$API_URL$path" -H 'Content-Type: application/json' \
    -H "X-Ifz-Timestamp: $ts" -H "X-Ifz-Signature: $signature" --data "$body")" || fail "Não consegui falar com a central do instaflowz. Confira a internet da VPS e tente de novo."
  code="${response##*$'\n'}"; response="${response%$'\n'*}"
  if [ "$code" != 200 ] && [ "$code" != 201 ]; then
    fail "$(jq -r '.error.message // "A central recusou o registro desta VPS."' <<<"$response" 2>/dev/null || echo 'A central recusou o registro desta VPS.')"
  fi
  INSTANCE_ID="$(jq -r .instance_id <<<"$response")"
  DOMAIN="$(jq -r .domain <<<"$response")"
  API_PUBLIC_KEY="$(jq -r .api_public_key <<<"$response")"
  # Os valores vão para o .env: só passam se tiverem exatamente o formato esperado.
  [[ "$INSTANCE_ID" =~ ^ins_[A-Za-z0-9_-]{8,64}$ ]] || fail "Resposta inesperada da central ao registrar a VPS (identificador)."
  [[ "$DOMAIN" =~ ^[a-z0-9]([a-z0-9-]{1,28}[a-z0-9])?\.instaflowz\.com\.br$ ]] || fail "Resposta inesperada da central ao registrar a VPS (endereço)."
  [[ "$API_PUBLIC_KEY" =~ ^[A-Za-z0-9+/]{40,120}={0,2}$ ]] || fail "Resposta inesperada da central ao registrar a VPS (chave)."
  flush_events
  done_step endereco "Endereço criado: $DOMAIN"
}

ssh_ports() {
  local ports=""
  [ -n "${SSH_CONNECTION:-}" ] && ports="$(awk '{print $4}' <<<"$SSH_CONNECTION")"
  if command -v sshd >/dev/null 2>&1; then ports="$ports $(sshd -T 2>/dev/null | awk '/^port / {print $2}')"; fi
  # shellcheck disable=SC2086
  printf '%s\n' $ports 22 | grep -E '^[0-9]+$' | sort -un | tr '\n' ' '
}

open_ports() {
  step portas "Conferindo e liberando as portas 80 e 443"
  local busy
  busy="$(ss -ltnpH '( sport = :80 or sport = :443 )' 2>/dev/null | grep -vE 'docker-proxy|caddy' || true)"
  if [ -n "$busy" ]; then
    if grep -qiE 'httpd|apache|nginx|cpanel|litespeed' <<<"$busy"; then
      fail "Esta VPS já tem um servidor web (provavelmente cPanel/Apache) usando as portas 80 e 443. Reinstale a VPS com Ubuntu 22.04 sem cPanel."
    fi
    fail "Outro programa já usa as portas 80 ou 443 nesta VPS. Reinstale a VPS com Ubuntu 22.04 limpo."
  fi
  local ports port
  ports="$(ssh_ports)"
  if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q 'Status: active'; then
    for port in $ports; do ufw allow "$port/tcp" >/dev/null; done
    ufw allow 80/tcp >/dev/null; ufw allow 443/tcp >/dev/null; ufw allow 443/udp >/dev/null
  fi
  if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
    for port in $ports; do firewall-cmd -q --permanent --add-port="$port/tcp"; done
    firewall-cmd -q --permanent --add-service=http --add-service=https && firewall-cmd -q --reload
  fi
  # Imagens da Oracle Cloud vêm com regras de iptables que recusam tudo menos o SSH.
  if command -v iptables >/dev/null 2>&1 && iptables -S INPUT 2>/dev/null | grep -qE -- '-j (REJECT|DROP)'; then
    iptables -C INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p tcp -m multiport --dports 80,443 -j ACCEPT
    iptables -C INPUT -p udp --dport 443 -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p udp --dport 443 -j ACCEPT
    if command -v netfilter-persistent >/dev/null 2>&1; then netfilter-persistent save >/dev/null 2>&1 || true
    elif [ -d /etc/iptables ]; then iptables-save > /etc/iptables/rules.v4; fi
  fi
  if [ -d /etc/fail2ban ]; then
    printf '[sshd]\nenabled = true\nport = %s\nmaxretry = 6\nbantime = 1h\n' "$(tr ' ' ',' <<<"${ports% }")" > /etc/fail2ban/jail.d/instaflowz.conf
    systemctl enable --now fail2ban >/dev/null 2>&1 && systemctl restart fail2ban >/dev/null 2>&1 || true
  fi
  done_step portas "Portas 80 e 443 liberadas · SSH mantido em $(tr ' ' ',' <<<"${ports% }")"
}

ensure_swap() {
  step memoria "Conferindo a memória"
  local swap_mb mem_mb
  swap_mb="$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo)"
  mem_mb="$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)"
  if [ "$swap_mb" -lt 1024 ] && [ "$mem_mb" -lt 4096 ] && [ ! -f /swapfile ]; then
    fallocate -l 2G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=2048 status=none
    chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile
    grep -q '^/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
    done_step memoria "Memória extra (swap) de 2 GB criada"
  else
    done_step memoria "Memória ok"
  fi
}

ensure_docker() {
  step docker "Instalando o Docker"
  if ! command -v docker >/dev/null 2>&1; then
    if [ "$PKG" = apt ]; then
      curl -fsSL https://get.docker.com | sh >/dev/null 2>&1
    else
      dnf -y -q install dnf-plugins-core >/dev/null
      dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo >/dev/null
      dnf -y -q install docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null
    fi
  fi
  systemctl enable --now docker >/dev/null 2>&1
  docker compose version >/dev/null 2>&1 || fail "O Docker foi instalado sem o Docker Compose. Rode o instalador de novo."
  done_step docker "Docker $(docker version --format '{{.Server.Version}}' 2>/dev/null) pronto"
}

# Baixa e confere o manifesto assinado. Depois disso, $WORK/release.json é confiável.
fetch_manifest() {
  [ -s "$WORK/release.json" ] && return 0
  curl -fsS -m 30 "$RELEASE_URL/release.json" -o "$WORK/release.json"
  curl -fsS -m 30 "$RELEASE_URL/release.json.sig" -o "$WORK/release.sig.b64"
  printf '%s\n' "$RELEASE_PUBLIC_KEY" > "$WORK/release.pub"
  base64 -d "$WORK/release.sig.b64" > "$WORK/release.sig"
  openssl pkeyutl -verify -pubin -inkey "$WORK/release.pub" -rawin -in "$WORK/release.json" -sigfile "$WORK/release.sig" >/dev/null 2>&1 \
    || { rm -f "$WORK/release.json"; fail "A versão publicada não passou na verificação de assinatura. Nada foi instalado. Avise o suporte."; }
}

resolve_image() {
  step imagem "Baixando o instaflowz"
  if [ -n "$BUILD_DIR" ]; then
    docker build -q -t instaflowz:local --build-arg VERSION="$VERSION-local" "$BUILD_DIR" >/dev/null
    IMAGE="instaflowz:local"
  elif [ -n "$IMAGE_REF" ]; then
    docker pull -q "$IMAGE_REF" >/dev/null
    IMAGE="$IMAGE_REF"
  else
    [ -n "$RELEASE_PUBLIC_KEY" ] || fail "Este instalador ainda não tem a chave de publicação. Use --imagem ou --build."
    fetch_manifest
    IMAGE="$(jq -r '.image + "@" + .digest' "$WORK/release.json")"
    VERSION="$(jq -r .version "$WORK/release.json")"
    CADDY_IMAGE="$(jq -r --arg d "$CADDY_IMAGE" '.caddy_image // $d' "$WORK/release.json")"
    [[ "$IMAGE" =~ ^ghcr\.io/[a-z0-9_.-]+/instaflowz@sha256:[a-f0-9]{64}$ ]] || fail "Manifesto de versão com imagem inesperada."
    docker pull -q "$IMAGE" >/dev/null
  fi
  done_step imagem "instaflowz $VERSION baixado"
}

write_files() {
  umask 077
  cat > "$INSTALL_DIR/.env" <<EOF
INSTAFLOWZ_DOMAIN=$DOMAIN
INSTAFLOWZ_IMAGE=$IMAGE
INSTAFLOWZ_API_URL=$([ "$OFFLINE" = 1 ] || printf '%s' "$API_URL")
INSTAFLOWZ_INSTANCE_ID=$INSTANCE_ID
INSTAFLOWZ_API_PUBLIC_KEY=$API_PUBLIC_KEY
INSTAFLOWZ_PUBLIC_IP=$PUBLIC_IP
INSTAFLOWZ_VERSION=$VERSION
INSTAFLOWZ_CADDY_IMAGE=$CADDY_IMAGE
EOF
  chmod 600 "$INSTALL_DIR/.env"
  mkdir -p "$INSTALL_DIR/caddy"
  cat > "$INSTALL_DIR/caddy/Caddyfile" <<'EOF'
{
	admin off
	on_demand_tls {
		ask http://app:3001/internal/caddy/ask
	}
}

https:// {
	tls {
		on_demand
	}
	request_body {
		max_size 2MB
	}
	encode zstd gzip
	header {
		Strict-Transport-Security "max-age=31536000"
		-Server
	}
	reverse_proxy app:3000
}

http:// {
	redir https://{host}{uri} permanent
}
EOF
  chmod 644 "$INSTALL_DIR/caddy/Caddyfile"
  cat > "$INSTALL_DIR/docker-compose.yml" <<EOF
name: instaflowz
services:
  app:
    image: \${INSTAFLOWZ_IMAGE}
    restart: unless-stopped
    env_file: .env
    environment:
      INSTAFLOWZ_DATA_DIR: /data
    volumes:
      - ./data:/data
    secrets: [master_key, session_secret, webhook_verify_token, instance_key, setup_token, setup_code]
    read_only: true
    tmpfs: [/tmp]
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    mem_limit: 768m
    pids_limit: 200
    networks: [interna]
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3001/internal/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 30s
      timeout: 5s
      retries: 3
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }
  caddy:
    image: \${INSTAFLOWZ_CADDY_IMAGE:-$CADDY_IMAGE}
    restart: unless-stopped
    ports: ["80:80", "443:443", "443:443/udp"]
    cap_drop: [ALL]
    cap_add: [NET_BIND_SERVICE]
    security_opt: ["no-new-privileges:true"]
    mem_limit: 256m
    pids_limit: 100
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on: [app]
    networks: [interna]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }
secrets:
  master_key: { file: ./secrets/master_key }
  session_secret: { file: ./secrets/session_secret }
  webhook_verify_token: { file: ./secrets/webhook_verify_token }
  instance_key: { file: ./secrets/instance_key }
  setup_token: { file: ./secrets/setup_token }
  setup_code: { file: ./secrets/setup_code }
networks:
  interna: {}
volumes:
  caddy_data: {}
  caddy_config: {}
EOF
  chmod 600 "$INSTALL_DIR/docker-compose.yml"
}

install_cli() {
  # O CLI roda como root pelo cron: só vem do código enviado (--build) ou baixado
  # e conferido com o sha256 do manifesto assinado. Nunca de uma pasta como /tmp.
  if [ -n "$BUILD_DIR" ] && [ -f "$BUILD_DIR/installer/instaflowz" ]; then
    install -m 755 "$BUILD_DIR/installer/instaflowz" /usr/local/bin/instaflowz
  elif curl -fsS -m 30 "$RELEASE_URL/instaflowz" -o "$WORK/instaflowz" 2>/dev/null; then
    if [ -n "$RELEASE_PUBLIC_KEY" ]; then
      fetch_manifest
      [ "$(sha256sum "$WORK/instaflowz" | cut -d' ' -f1)" = "$(jq -r '.cli_sha256 // ""' "$WORK/release.json")" ] \
        || fail "O comando instaflowz baixado não confere com a versão publicada. Avise o suporte."
    fi
    install -m 755 "$WORK/instaflowz" /usr/local/bin/instaflowz
  else
    info "Não consegui baixar o comando instaflowz. O sistema funciona sem ele."
  fi
  [ -n "$RELEASE_PUBLIC_KEY" ] && printf '%s\n' "$RELEASE_PUBLIC_KEY" > "$INSTALL_DIR/release.pub"
  printf '%s\n' "$RELEASE_URL" > "$INSTALL_DIR/release.url"
  if [ -z "$BUILD_DIR" ] && [ -z "$IMAGE_REF" ]; then
    printf 'SHELL=/bin/bash\n%d 4 * * * root /usr/local/bin/instaflowz atualizar --silencioso >> /var/log/instaflowz-update.log 2>&1\n' "$((RANDOM % 60))" > /etc/cron.d/instaflowz
  fi
}

start_services() {
  step iniciando "Ligando o sistema"
  cd "$INSTALL_DIR"
  docker compose pull -q caddy >/dev/null 2>&1 || true
  docker compose up -d --remove-orphans >/dev/null
  local i
  for i in $(seq 1 60); do
    if [ "$(docker inspect -f '{{.State.Health.Status}}' instaflowz-app-1 2>/dev/null)" = healthy ]; then break; fi
    sleep 2
  done
  [ "$(docker inspect -f '{{.State.Health.Status}}' instaflowz-app-1 2>/dev/null)" = healthy ] \
    || fail "O sistema não ligou. Veja com: instaflowz logs"
  done_step iniciando "instaflowz rodando"
}

wait_certificate() {
  step certificado "Emitindo o cadeado HTTPS"
  local i
  # Pede direto ao Caddy local: na Oracle Cloud a VPS nem sempre alcança o próprio IP público.
  for i in $(seq 1 45); do
    if curl -fsS -m 10 -o /dev/null --resolve "$DOMAIN:443:127.0.0.1" "https://$DOMAIN/api/state" 2>/dev/null; then
      done_step certificado "Cadeado HTTPS emitido"
      return
    fi
    sleep 4
  done
  info "O cadeado ainda está sendo emitido. Se o endereço não abrir em alguns minutos, rode: instaflowz diagnostico"
  [ "$OFFLINE" = 1 ] || send_event certificado ok "Cadeado em emissão; pode levar alguns minutos"
}

finish() {
  local url="https://$DOMAIN" token code
  token="$(cat "$SECRETS/setup_token")"
  code="$(cat "$SECRETS/setup_code")"
  CURRENT_STEP=concluido
  if [ "$OFFLINE" = 0 ]; then
    local body path sig ts signature
    path="/v1/instances/$INSTANCE_ID/events"
    body="$(jq -nc --arg u "$url" --arg l "$url/configurar#token=$token" '{step:"concluido",status:"ok",message:"Instalação concluída",data:{url:$u,login_url:$l}}')"
    sig="$(sign POST "$path" "$body")"; ts="${sig%% *}"; signature="${sig#* }"
    curl -fsS -m 10 -X POST "$API_URL$path" -H 'Content-Type: application/json' \
      -H "X-Ifz-Instance: $INSTANCE_ID" -H "X-Ifz-Timestamp: $ts" -H "X-Ifz-Signature: $signature" --data "$body" >/dev/null 2>&1 || true
  fi
  printf '\n  %s\n  %s\n' "Pronto! Abra no navegador:" "$(color '35' "$url")"
  # O código só aparece no terminal de quem instalou; pela área do aluno o login já vem no link.
  if [ "$TERMINAL" = 1 ]; then printf '  Código de instalação: %s\n' "$(color '35' "$code")"; fi
  printf '\n'
}

main() {
  printf '\n  %s · instalador %s\n\n' "$(color '35' 'instaflowz')" "$VERSION"
  mkdir -p "$INSTALL_DIR"
  touch /var/log/instaflowz-install.log && chmod 600 /var/log/instaflowz-install.log
  # Pelo terminal, mostra e grava. Pela área do aluno a saída já vai para o log.
  if [ -t 1 ]; then TERMINAL=1; exec > >(tee -a /var/log/instaflowz-install.log) 2>&1; fi
  check_basics
  install_packages
  create_secrets
  register
  open_ports
  ensure_swap
  ensure_docker
  resolve_image
  write_files
  install_cli
  start_services
  wait_certificate
  finish
}

main "$@"
