#!/usr/bin/env bash
# ==============================================================================
# Cardumen AI — Bootstrap de instalación (acceso por invitación · binario nativo)
# ------------------------------------------------------------------------------
# Servido público en https://get.cardumen.ai/install.sh
# NO clona ningún repo, NO deja fuentes en disco. Activa la licencia contra el
# servidor, canjea una URL presignada de S3/CloudFront y baja UN solo binario
# nativo `cardumen` (+ su .sha256 y .sig), lo verifica FAIL-CLOSED y lo instala.
#
# USO:
#   curl -fsSL https://get.cardumen.ai/install.sh | bash -s -- <CARD-CODE> <email>
#   # o interactivo:
#   curl -fsSL https://get.cardumen.ai/install.sh | bash
# ==============================================================================

set -uo pipefail

API="${CARDUMEN_LICENSE_API:-https://api.cardumen.ai}"
USER_DIR="${CARDUMEN_USER_DIR:-${HOME}/.cardumen}"
INSTALL_DIR="${CARDUMEN_INSTALL_DIR:-${HOME}/.local/bin}"
BINARY="cardumen"
# Clave pública minisign/Ed25519 embebida (verificación de firma del binario).
# Debe coincidir con la privada que usa la CI de release para firmar (.sig).
# NOTA integrador: reemplazar por la pública de PRODUCCIÓN antes de lanzar.
MINISIGN_PUBKEY="${CARDUMEN_MINISIGN_PUBKEY:-RWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}"

CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
log()  { echo -e "${CYAN}▸${NC} $*"; }
ok()   { echo -e "  ${GREEN}✓${NC} $*"; }
warn() { echo -e "  ${YELLOW}⚠${NC} $*" >&2; }
die()  { echo -e "  ${RED}✗${NC} $*" >&2; exit 1; }

INVITE="${1:-}"; EMAIL="${2:-}"

for dep in curl openssl uname; do
  command -v "$dep" >/dev/null 2>&1 || die "necesito '$dep' instalado."
done

# ── Pedir invitación / email si faltan (modo interactivo) ──────────────────
if [ -z "$INVITE" ]; then
  if [ -t 0 ]; then printf '  Código de invitación: '; read -r INVITE; fi
fi
[ -n "$INVITE" ] || die "falta el código de invitación. Uso: install.sh <CARD-CODE> <email>"
if [ -z "$EMAIL" ]; then
  if [ -t 0 ]; then printf '  Email: '; read -r EMAIL; fi
fi
[ -n "$EMAIL" ] || die "falta el email. Uso: install.sh <CARD-CODE> <email>"

# ── Detección os/arch (matchea el layout de S3: {darwin|linux|windows}/{arm64|x86_64}) ─
detect_platform() {
  local os arch
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$os" in
    Linux)   OS="linux" ;;
    Darwin)  OS="darwin" ;;
    *)       die "SO no soportado: $os (usa install.ps1 en Windows)" ;;
  esac
  case "$arch" in
    x86_64|amd64)  ARCH="x86_64" ;;
    aarch64|arm64) ARCH="arm64" ;;
    *)             die "arquitectura no soportada: $arch" ;;
  esac
}
detect_platform
log "Plataforma: ${OS}/${ARCH}"

# ── Device id estable (mismo algoritmo que license.rs::device_id y lib/license.sh) ─
# sha256("cardumen-device:{raw}") en hex, primeros 32 chars. Cacheado en device.id.
mkdir -p "$USER_DIR" || die "no pude crear ${USER_DIR}"
if [ -f "${USER_DIR}/device.id" ]; then
  DEVICE="$(tr -d '[:space:]' < "${USER_DIR}/device.id")"
fi
if [ -z "${DEVICE:-}" ]; then
  if [ -r /etc/machine-id ]; then RAW="$(tr -d '[:space:]' < /etc/machine-id)"
  elif command -v ioreg >/dev/null 2>&1; then
    RAW="$(ioreg -rd1 -c IOPlatformExpertDevice 2>/dev/null | awk -F'"' '/IOPlatformUUID/{print $4}')"
  fi
  [ -n "${RAW:-}" ] || RAW="$(hostname 2>/dev/null || echo unknown-host)-$(id -u 2>/dev/null || echo 0)"
  DEVICE="$(printf 'cardumen-device:%s' "$RAW" | openssl dgst -sha256 | awk '{print $NF}' | cut -c1-32)"
  printf '%s\n' "$DEVICE" > "${USER_DIR}/device.id"; chmod 600 "${USER_DIR}/device.id"
fi
[ -n "$DEVICE" ] || die "no pude derivar un device id estable."
LABEL="$(hostname 2>/dev/null || echo unknown)"

# ── JSON helper (escapa comillas/backslashes en los valores del usuario) ────
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }

# ── HTTP helper: POST JSON capturando cuerpo + código HTTP (para errores claros) ─
HTTP=""; RESP=""
api_post() {
  local out
  out="$(curl -sS -m 20 -X POST "$1" -H 'content-type: application/json' -d "$2" -w $'\n%{http_code}' 2>/dev/null)" \
    || { HTTP="000"; RESP=""; return 0; }
  HTTP="${out##*$'\n'}"; RESP="${out%$'\n'*}"
}
err_code() { printf '%s' "$1" | grep -oE '"error"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//'; }
# Traduce el código de error del servidor a un motivo humano y accionable.
explain_err() {
  case "$1" in
    payment_required)           echo "tu suscripción no está activa. Renueva o completa tu compra en https://cardumen.ai/account" ;;
    revoked|expired)            echo "esta licencia fue revocada o expiró. Escríbenos al equipo." ;;
    email_mismatch)             echo "el email no coincide con el de la invitación." ;;
    already_bound_other_device) echo "esta licencia ya está activada en otro equipo (una por dispositivo)." ;;
    invite_not_found)           echo "el código de invitación no existe." ;;
    unsupported_platform)       echo "tu sistema/arquitectura aún no está soportado." ;;
    no_release)                 echo "todavía no hay una versión publicada para tu plataforma." ;;
    rate_limited)               echo "demasiados intentos; espera unos segundos y reintenta." ;;
    bad_license)                echo "la licencia no pudo verificarse; reintenta o contacta al equipo." ;;
    ""|000)                     echo "no hubo respuesta del servidor (revisa tu conexión)." ;;
    *)                          echo "el servidor rechazó la solicitud (${1})." ;;
  esac
}

# ── 1. Activar: canjear invitación por licencia firmada (Ed25519 JWT) ──────
log "Activando licencia para ${EMAIL}…"
ACT_BODY="$(printf '{"invite_code":"%s","email":"%s","device_id":"%s","device_label":"%s"}' \
  "$(json_escape "$INVITE")" "$(json_escape "$EMAIL")" "$(json_escape "$DEVICE")" "$(json_escape "$LABEL")")"
api_post "${API}/activate" "$ACT_BODY"
[ "$HTTP" = "200" ] || die "no se pudo activar: $(explain_err "$(err_code "$RESP")")"
JWT="$(printf '%s' "$RESP" | grep -oE '"license_jwt"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//')"
[ -n "$JWT" ] || die "el servidor no devolvió una licencia válida."
printf '%s\n' "$JWT" > "${USER_DIR}/license.jwt"; chmod 600 "${USER_DIR}/license.jwt"
date -u +%s > "${USER_DIR}/.last-validate" 2>/dev/null || true
ok "Licencia activada (${USER_DIR}/license.jwt)."

# ── 2. Descarga: la licencia canjea una URL presignada de S3/CloudFront ────
# body {license, os, arch} → {url, version, sha256, sig_url}  (TTL corto ~90s)
log "Solicitando descarga del binario…"
DL_BODY="$(printf '{"license":"%s","os":"%s","arch":"%s"}' \
  "$(json_escape "$JWT")" "$OS" "$ARCH")"
api_post "${API}/download" "$DL_BODY"
[ "$HTTP" = "200" ] || die "no se pudo autorizar la descarga: $(explain_err "$(err_code "$RESP")")"
DL_URL="$(printf '%s' "$RESP" | grep -oE '"url"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//')"
DL_VER="$(printf '%s' "$RESP" | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//')"
DL_SHA="$(printf '%s' "$RESP" | grep -oE '"sha256"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//')"
DL_SIG_URL="$(printf '%s' "$RESP" | grep -oE '"sig_url"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*:"//; s/"$//')"
[ -n "$DL_URL" ] || die "el servidor no devolvió una URL de descarga."
ok "Autorizado: cardumen ${DL_VER:-?} (${OS}/${ARCH})."

# ── 3. Descargar binario + firma a un dir temporal (nunca en su sitio final) ─
TMP="$(mktemp -d "${TMPDIR:-/tmp}/cardumen-install.XXXXXX")" || die "no pude crear el temporal."
trap 'rm -rf "$TMP"' EXIT
BIN_TMP="${TMP}/${BINARY}"
SIG_TMP="${TMP}/${BINARY}.sig"

log "Descargando binario…"
curl -fsSL -m 300 "$DL_URL" -o "$BIN_TMP" || die "fallo al descargar el binario."
if [ -n "$DL_SIG_URL" ]; then
  curl -fsSL -m 60 "$DL_SIG_URL" -o "$SIG_TMP" 2>/dev/null || warn "no pude bajar la firma (.sig)."
fi

# ── 4. Verificación FAIL-CLOSED: checksum sha256 + firma Ed25519 ────────────
# 4a. Checksum (siempre exigido: el servidor lo entrega junto al presign).
compute_sha256() {
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
  else openssl dgst -sha256 "$1" | awk '{print $NF}'; fi
}
[ -n "$DL_SHA" ] || die "el servidor no entregó el sha256 esperado — no puedo verificar (fail-closed)."
GOT_SHA="$(compute_sha256 "$BIN_TMP")"
# Comparación case-insensitive del hex.
if [ "$(printf '%s' "$GOT_SHA" | tr '[:upper:]' '[:lower:]')" != "$(printf '%s' "$DL_SHA" | tr '[:upper:]' '[:lower:]')" ]; then
  die "checksum NO coincide (esperado ${DL_SHA}, obtenido ${GOT_SHA}). Descarga corrupta o manipulada."
fi
ok "Checksum sha256 verificado."

# 4b. Firma detached Ed25519 (minisign). FAIL-CLOSED: si hay .sig o pubkey real,
# la firma DEBE verificar. Solo se salta si no hay firma disponible Y la pubkey
# sigue siendo el placeholder (entorno de desarrollo pre-firma-de-código).
verify_signature() {
  local bin="$1" sig="$2"
  if command -v minisign >/dev/null 2>&1; then
    minisign -Vm "$bin" -x "$sig" -P "$MINISIGN_PUBKEY" >/dev/null 2>&1
    return $?
  fi
  # Fallback: verificación cruda Ed25519 con openssl si tenemos la pubkey en PEM.
  if [ -n "${CARDUMEN_ED25519_PUBKEY_PEM:-}" ]; then
    local pem="${TMP}/pub.pem"
    printf '%s\n' "$CARDUMEN_ED25519_PUBKEY_PEM" > "$pem"
    openssl pkeyutl -verify -pubin -inkey "$pem" -rawin -in "$bin" -sigfile "$sig" >/dev/null 2>&1
    return $?
  fi
  return 2  # no verifier disponible
}

PLACEHOLDER_PUBKEY='RWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
if [ -f "$SIG_TMP" ]; then
  if verify_signature "$BIN_TMP" "$SIG_TMP"; then
    ok "Firma Ed25519 verificada."
  else
    rc=$?
    if [ "$rc" = "2" ]; then
      die "no hay verificador de firma (instala 'minisign' o exporta CARDUMEN_ED25519_PUBKEY_PEM). No instalo sin verificar (fail-closed)."
    fi
    die "FIRMA INVÁLIDA — el binario NO está firmado por Cardumen. Abortando (fail-closed)."
  fi
elif [ "$MINISIGN_PUBKEY" != "$PLACEHOLDER_PUBKEY" ]; then
  die "falta la firma (.sig) del binario y la verificación es obligatoria. Abortando (fail-closed)."
else
  warn "sin firma verificable (entorno pre-release): solo checksum. NO para producción."
fi

# ── 5. Instalar en ~/.local/bin/cardumen (atómico) ─────────────────────────
mkdir -p "$INSTALL_DIR" || die "no pude crear ${INSTALL_DIR}"
chmod +x "$BIN_TMP"
DEST="${INSTALL_DIR}/${BINARY}"
# Instalación atómica: escribe a .new en el mismo dir (mismo filesystem) y luego
# renombra encima del destino (rename atómico), evitando un binario a medias.
mv -f "$BIN_TMP" "${DEST}.new" || die "no pude escribir ${DEST}.new."
mv -f "${DEST}.new" "$DEST" || die "no pude instalar en ${DEST}."
ok "Instalado: ${DEST}"

# ── 6. Asegurar PATH (persistente + activación inmediata) ───────────────────
# Deja ${INSTALL_DIR} en el PATH del shell del usuario. CLAVE: crea el rc si NO
# existe (en una máquina/VM fresca no hay ~/.zshrc → antes el PATH no se persistía
# ni para terminales nuevas). Un `curl | sh` NO puede hacer `source` en el shell
# padre (un hijo no cambia el env del padre), por eso además imprimimos el comando
# exacto para activarlo en la terminal ACTUAL.
IN_PATH=0
case ":${PATH}:" in *":${INSTALL_DIR}:"*) IN_PATH=1 ;; esac
ensure_path() {
  [ "$IN_PATH" = 1 ] && return 0
  local line="export PATH=\"${INSTALL_DIR}:\$PATH\""
  local shell_name primary rcs rc
  shell_name="$(basename "${SHELL:-/bin/sh}")"
  case "$shell_name" in
    zsh)  primary="${ZDOTDIR:-$HOME}/.zshrc"; rcs="${ZDOTDIR:-$HOME}/.zshrc ${HOME}/.zprofile ${HOME}/.profile" ;;
    bash) primary="${HOME}/.bashrc";          rcs="${HOME}/.bashrc ${HOME}/.bash_profile ${HOME}/.profile" ;;
    *)    primary="${HOME}/.profile";          rcs="${HOME}/.profile" ;;
  esac
  [ -f "$primary" ] || { : > "$primary" 2>/dev/null || true; }   # crea el rc si falta
  for rc in $rcs; do
    [ -f "$rc" ] || continue
    grep -qF "$INSTALL_DIR" "$rc" 2>/dev/null || printf '\n# Cardumen CLI\n%s\n' "$line" >> "$rc"
  done
  ok "PATH configurado en ${primary} (las terminales nuevas ya tendrán 'cardumen')."
}
ensure_path

echo ""
ok "Cardumen instalado: ${DEST}"
if [ "$IN_PATH" != 1 ]; then
  printf '\n  %bPara usar cardumen AHORA en esta terminal, corre:%b\n' "${YELLOW}" "${NC}"
  printf '    %bexport PATH="%s:$PATH"%b\n' "${CYAN}" "${INSTALL_DIR}" "${NC}"
  printf '  (o reinicia el shell:  exec "$SHELL")  ·  las terminales nuevas ya lo tienen.\n'
fi

# Cheat-sheet de comandos principales (los primeros que usa un usuario nuevo).
printf '\n  %bPrimeros comandos:%b\n' "${BOLD}" "${NC}"
printf '    %bcardumen%b                 abre el agente en modo interactivo (TUI)\n' "${CYAN}" "${NC}"
printf '    %bcardumen -p "arregla X"%b  una sola instrucción y termina (one-shot)\n' "${CYAN}" "${NC}"
printf '    %bcardumen run "…"%b         corre una tarea con el loop agéntico\n' "${CYAN}" "${NC}"
printf '    %bcardumen --help%b          lista todos los comandos y flags\n' "${CYAN}" "${NC}"
printf '  Guía completa:  %bhttps://cardumen.ai/instalar%b\n\n' "${CYAN}" "${NC}"
