#!/usr/bin/env bash

set -euo pipefail

INSTALL_DIR="${INSTALL_DIR:-/opt/ia-system}"
RELEASE_URL="${RELEASE_URL:-https://install.leadflow.ia.br/ia-system.tar.gz}"
RELEASE_CHECKSUM_URL="${RELEASE_CHECKSUM_URL:-${RELEASE_URL}.sha256}"
SKIP_RELEASE_CHECKSUM="${SKIP_RELEASE_CHECKSUM:-0}"
SKIP_IMAGE_PULL="${SKIP_IMAGE_PULL:-0}"
LOCAL_BUILD="${LOCAL_BUILD:-0}"
DOMAIN="${DOMAIN:-}"
DOMAIN_ALIASES="${DOMAIN_ALIASES:-}"
ACME_EMAIL="${ACME_EMAIL:-}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
GHCR_OWNER="${GHCR_OWNER:-iasystem}"
FORCE_REINSTALL="${FORCE_REINSTALL:-0}"
CONFIRM_REINSTALL="${CONFIRM_REINSTALL:-}"
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-180}"
LOCAL_INSTALL=0
NO_START=0
DRY_RUN=0

log() {
  printf '[ia-system] %s\n' "$*"
}

warn() {
  printf '[ia-system] WARN: %s\n' "$*" >&2
}

die() {
  printf '[ia-system] ERROR: %s\n' "$*" >&2
  exit 1
}

usage() {
  cat <<'EOF'
Usage: sudo ./install.sh [options]

Options:
  --domain DOMAIN       Public domain for the app
  --alias DOMAIN        Additional domain served by Caddy, repeatable
  --email EMAIL         Email used by Caddy/Let's Encrypt
  --tag TAG             Container image tag (default: latest)
  --ghcr-owner OWNER    GHCR owner/organization for published images
  --install-dir PATH    Install directory (default: /opt/ia-system)
  --local               Install from current working tree
  --no-start            Generate files but do not start containers
  --dry-run             Validate host and print the install plan without changes
  --force-reinstall     Destroy the existing install directory and Docker volumes
  -h, --help            Show help

Environment:
  RELEASE_URL             Release archive URL
  RELEASE_CHECKSUM_URL    SHA-256 checksum URL (default: RELEASE_URL.sha256)
  DOMAIN_ALIASES          Space-separated additional domains
  FORCE_REINSTALL         Set to 1 to reinstall over an existing install
  CONFIRM_REINSTALL       Must be YES when FORCE_REINSTALL=1
  HEALTH_TIMEOUT          Seconds to wait for the app health endpoint
  LOCAL_BUILD             Build app images on the host instead of pulling (0/1)
  SKIP_IMAGE_PULL         Skip docker compose pull (0/1)
  SKIP_RELEASE_CHECKSUM   Set to 1 only for internal/local tests
EOF
}

append_domain_alias() {
  local alias="$1"
  if [ -z "$DOMAIN_ALIASES" ]; then
    DOMAIN_ALIASES="$alias"
  else
    DOMAIN_ALIASES="$DOMAIN_ALIASES $alias"
  fi
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --domain) DOMAIN="${2:?missing domain}"; shift 2 ;;
    --alias|--domain-alias) append_domain_alias "${2:?missing alias domain}"; shift 2 ;;
    --email) ACME_EMAIL="${2:?missing email}"; shift 2 ;;
    --tag) IMAGE_TAG="${2:?missing tag}"; shift 2 ;;
    --ghcr-owner) GHCR_OWNER="${2:?missing owner}"; shift 2 ;;
    --install-dir) INSTALL_DIR="${2:?missing path}"; shift 2 ;;
    --local) LOCAL_INSTALL=1; shift ;;
    --no-start) NO_START=1; shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    --force-reinstall) FORCE_REINSTALL=1; shift ;;
    -h|--help) usage; exit 0 ;;
    *) echo "Unknown option: $1" >&2; usage; exit 2 ;;
  esac
done

if [ "$DRY_RUN" != "1" ]; then
  [ "$(id -u)" -eq 0 ] || die "Run with sudo/root"
elif [ "$(id -u)" -ne 0 ]; then
  warn "dry-run without root; privileged install checks are informational only"
fi

if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
else
  SCRIPT_DIR="$(pwd)"
fi
if [ -f "$SCRIPT_DIR/scripts/lib/secrets.sh" ]; then
  # shellcheck disable=SC1091
  . "$SCRIPT_DIR/scripts/lib/secrets.sh"
else
  secret_hex() { openssl rand -hex "${1:-32}"; }
  secret_base64() { openssl rand -base64 "${1:-32}" | tr -d '\n'; }
  format_env_value() {
    local value="$1"
    case "$value" in
      *[[:space:]#\"\\\$\`]*)
        value="$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\$/\\$/g; s/`/\\`/g')"
        printf '"%s"' "$value"
        ;;
      *)
        printf '%s' "$value"
        ;;
    esac
  }
  set_env_value() {
    local file="$1" key="$2" value="$3" line tmp
    value="$(format_env_value "$value")"
    line="${key}=${value}"
    tmp="$(mktemp)"
    awk -v key="$key" -v line="$line" '
      BEGIN { done = 0 }
      $0 ~ "^" key "=" { print line; done = 1; next }
      { print }
      END { if (!done) print line }
    ' "$file" > "$tmp"
    cat "$tmp" > "$file"
    rm -f "$tmp"
  }
fi

need_or_install_docker() {
  if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
    return 0
  fi
  command -v apt-get >/dev/null 2>&1 || die "automatic Docker install requires Ubuntu/Debian with apt-get"
  if [ "$DRY_RUN" = "1" ]; then
    log "dry-run: Docker/Compose not found; installer would install Docker"
    return 0
  fi
  apt-get update
  apt-get install -y ca-certificates curl gnupg openssl tar
  curl -fsSL https://get.docker.com | sh
  apt-get install -y docker-compose-plugin || true
}

prompt_if_empty() {
  local var_name="$1"
  local prompt="$2"
  local current="${!var_name:-}"
  if [ -z "$current" ]; then
    printf '%s: ' "$prompt"
    if [ -r /dev/tty ]; then
      read -r current < /dev/tty
    else
      die "missing $var_name and no TTY available"
    fi
    eval "$var_name=\"\$current\""
  fi
}

normalize_domain() {
  local value="$1"
  value="${value#http://}"
  value="${value#https://}"
  value="${value%%/*}"
  value="${value%.}"
  printf '%s' "$value"
}

validate_domain_name() {
  local value="$1"
  case "$value" in
    ''|.*|*..*|*-|*.-*|*_*|*[!A-Za-z0-9.-]*)
      die "invalid domain: $value"
      ;;
  esac
}

normalize_domains() {
  local alias normalized aliases_out=""

  DOMAIN="$(normalize_domain "$DOMAIN")"
  validate_domain_name "$DOMAIN"

  for alias in $DOMAIN_ALIASES; do
    normalized="$(normalize_domain "$alias")"
    validate_domain_name "$normalized"
    if [ -z "$aliases_out" ]; then
      aliases_out="$normalized"
    else
      aliases_out="$aliases_out $normalized"
    fi
  done
  DOMAIN_ALIASES="$aliases_out"
}

assert_safe_install_dir() {
  case "$INSTALL_DIR" in
    ''|'/'|'/opt'|'/usr'|'/var'|'/home'|'/root'|'/etc'|'/srv')
      die "unsafe install directory: $INSTALL_DIR"
      ;;
  esac
}

install_dir_has_files() {
  [ -d "$INSTALL_DIR" ] || return 1
  [ -n "$(find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 2>/dev/null | head -n 1)" ]
}

existing_install_detected() {
  [ -e "$INSTALL_DIR/.env" ] ||
    [ -e "$INSTALL_DIR/compose.prod.yml" ] ||
    install_dir_has_files
}

compose_down_existing_install() {
  [ -f "$INSTALL_DIR/compose.prod.yml" ] || return 0
  command -v docker >/dev/null 2>&1 || return 0

  if [ -f "$INSTALL_DIR/.env" ]; then
    docker compose --env-file "$INSTALL_DIR/.env" -f "$INSTALL_DIR/compose.prod.yml" down -v --remove-orphans || true
  else
    docker compose -f "$INSTALL_DIR/compose.prod.yml" down -v --remove-orphans || true
  fi
}

prepare_install_dir() {
  assert_safe_install_dir

  if existing_install_detected; then
    if [ "$FORCE_REINSTALL" != "1" ]; then
      die "$INSTALL_DIR is not empty. Use --force-reinstall with CONFIRM_REINSTALL=YES to destroy and reinstall."
    fi
    [ "$CONFIRM_REINSTALL" = "YES" ] ||
      die "destructive reinstall blocked. Reexecute with CONFIRM_REINSTALL=YES"

    log "removing existing Lead Flow IA installation from $INSTALL_DIR"
    compose_down_existing_install
    rm -rf "$INSTALL_DIR"
  fi

  mkdir -p "$INSTALL_DIR"
}

copy_local_tree() {
  mkdir -p "$INSTALL_DIR"
  tar \
    --exclude='.git' \
    --exclude='node_modules' \
    --exclude='.next' \
    --exclude='.env' \
    -C "$SCRIPT_DIR" \
    -cf - . | tar -C "$INSTALL_DIR" -xf -
}

sha256_file() {
  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
    die "sha256sum/shasum not found"
  fi
}

verify_release_checksum() {
  local archive="$1" checksum_file="$2" expected actual

  if [ "$SKIP_RELEASE_CHECKSUM" = "1" ]; then
    warn "SKIP_RELEASE_CHECKSUM=1; release archive integrity check skipped"
    return 0
  fi

  curl -fsSL "$RELEASE_CHECKSUM_URL" -o "$checksum_file" ||
    die "failed to download release checksum: $RELEASE_CHECKSUM_URL"

  expected="$(awk '{print $1; exit}' "$checksum_file")"
  case "$expected" in
    [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]*) ;;
    *) die "invalid release checksum file: $RELEASE_CHECKSUM_URL" ;;
  esac

  actual="$(sha256_file "$archive")"
  [ "$actual" = "$expected" ] ||
    die "release checksum mismatch for $RELEASE_URL"

  log "release checksum verified"
}

download_release() {
  mkdir -p "$INSTALL_DIR"
  tmp="$(mktemp -d)"
  curl -fsSL "$RELEASE_URL" -o "$tmp/ia-system.tar.gz"
  verify_release_checksum "$tmp/ia-system.tar.gz" "$tmp/ia-system.tar.gz.sha256"
  tar -xzf "$tmp/ia-system.tar.gz" -C "$tmp"
  src="$(find "$tmp" -mindepth 1 -maxdepth 1 -type d | head -1)"
  [ -n "$src" ] || die "Invalid release archive"
  tar -C "$src" -cf - . | tar -C "$INSTALL_DIR" -xf -
  rm -rf "$tmp"
}

render_env() {
  cd "$INSTALL_DIR"
  [ -f .env ] && return 0
  cp .env.template .env

  local pg_pass redis_pass minio_pass auth_secret engine_secret cron_secret
  local langfuse_secret langfuse_salt voice_tools voice_key meta_token
  pg_pass="$(secret_hex 24)"
  redis_pass="$(secret_hex 24)"
  minio_pass="$(secret_hex 24)"
  auth_secret="$(secret_base64 32)"
  engine_secret="$(secret_hex 32)"
  cron_secret="$(secret_hex 32)"
  langfuse_secret="$(secret_base64 32)"
  langfuse_salt="$(secret_base64 32)"
  voice_tools="$(secret_hex 32)"
  voice_key="$(secret_hex 32)"
  meta_token="$(secret_hex 16)"

  set_env_value .env DOMAIN "$DOMAIN"
  set_env_value .env DOMAIN_ALIASES "$DOMAIN_ALIASES"
  set_env_value .env APP_URL "https://$DOMAIN"
  set_env_value .env INSTALL_DIR "$INSTALL_DIR"
  set_env_value .env ACME_EMAIL "$ACME_EMAIL"
  set_env_value .env GHCR_OWNER "$GHCR_OWNER"
  set_env_value .env IMAGE_TAG "$IMAGE_TAG"
  set_env_value .env LANGFUSE_POSTGRES_DB "ia_system_langfuse"
  set_env_value .env POSTGRES_PASSWORD "$pg_pass"
  set_env_value .env DATABASE_URL "postgresql://ia_system_user:${pg_pass}@postgres:5432/ia_system"
  set_env_value .env REDIS_PASSWORD "$redis_pass"
  set_env_value .env REDIS_URL "redis://:${redis_pass}@redis:6379"
  set_env_value .env MINIO_ROOT_PASSWORD "$minio_pass"
  set_env_value .env BETTER_AUTH_SECRET "$auth_secret"
  set_env_value .env BETTER_AUTH_URL "https://$DOMAIN"
  set_env_value .env NEXT_PUBLIC_APP_URL "https://$DOMAIN"
  set_env_value .env LANGGRAPH_ENGINE_SECRET "$engine_secret"
  set_env_value .env LANGFUSE_SECRET "$langfuse_secret"
  set_env_value .env LANGFUSE_SALT "$langfuse_salt"
  set_env_value .env CRON_SECRET "$cron_secret"
  set_env_value .env VOICE_TOOLS_HMAC_SECRET "$voice_tools"
  set_env_value .env VOICE_SECRETS_KEY "$voice_key"
  set_env_value .env VOICE_WEBHOOK_BASE_URL "https://$DOMAIN"
  set_env_value .env META_VERIFY_TOKEN "$meta_token"
  chmod 600 .env
}

preflight() {
  [ "$(uname -s)" = "Linux" ] || die "installer supports Linux VPS hosts only"
  command -v apt-get >/dev/null 2>&1 || die "installer currently supports Ubuntu/Debian VPS hosts"

  local missing_base=0
  for cmd in openssl curl tar; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      missing_base=1
    fi
  done
  if [ "$missing_base" = "1" ]; then
    if [ "$DRY_RUN" = "1" ]; then
      warn "dry-run: missing base packages; installer would install ca-certificates curl openssl tar"
    else
      apt-get update
      apt-get install -y ca-certificates curl openssl tar
    fi
  fi

  prompt_if_empty DOMAIN "Domain"
  prompt_if_empty ACME_EMAIL "Email for SSL"
  [ -n "$DOMAIN" ] || die "DOMAIN is required"
  normalize_domains

  if [ -r /proc/meminfo ]; then
    mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo)"
    if [ "${mem_kb:-0}" -lt 3800000 ]; then
      warn "less than 4GB RAM detected; Lead Flow IA may be constrained"
    fi
  fi

  disk_kb="$(df -Pk "$(dirname "$INSTALL_DIR")" 2>/dev/null | awk 'NR==2 {print $4}')"
  if [ "${disk_kb:-0}" -gt 0 ] && [ "$disk_kb" -lt 35000000 ]; then
    warn "less than 35GB free disk detected under $(dirname "$INSTALL_DIR")"
  fi

}

check_public_ports_available() {
  if command -v ss >/dev/null 2>&1; then
    for port in 80 443; do
      if ss -ltn | awk '{print $4}' | grep -Eq "[:.]${port}$"; then
        die "port $port is already in use"
      fi
    done
  fi
}

wait_for_nextjs_health() {
  local elapsed=0 interval=3
  log "waiting for Next.js health endpoint"
  while [ "$elapsed" -lt "$HEALTH_TIMEOUT" ]; do
    if docker exec ia-nextjs node -e "fetch('http://localhost:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" >/dev/null 2>&1; then
      return 0
    fi
    sleep "$interval"
    elapsed=$((elapsed + interval))
  done
  die "Next.js health endpoint did not become ready within ${HEALTH_TIMEOUT}s"
}

log "running preflight"
preflight

if [ "$DRY_RUN" = "1" ]; then
  need_or_install_docker
  echo
  echo "Lead Flow IA dry-run OK"
  echo "Install dir: $INSTALL_DIR"
  echo "Domain: $DOMAIN"
  if [ -n "$DOMAIN_ALIASES" ]; then
    echo "Aliases: $DOMAIN_ALIASES"
  fi
  echo "SSL email: $ACME_EMAIL"
  echo "Image owner/tag: ghcr.io/$GHCR_OWNER/*:$IMAGE_TAG"
  if existing_install_detected; then
    if [ "$FORCE_REINSTALL" = "1" ]; then
      echo "Existing install: would be destroyed and recreated"
    else
      echo "Existing install: detected; normal install would stop before changes"
    fi
  fi
  if [ "$LOCAL_INSTALL" = "1" ]; then
    echo "Source: local tree ($SCRIPT_DIR)"
  else
    echo "Source: release archive ($RELEASE_URL)"
    if [ "$SKIP_RELEASE_CHECKSUM" = "1" ]; then
      echo "Checksum: skipped"
    else
      echo "Checksum: $RELEASE_CHECKSUM_URL"
    fi
  fi
  echo "No files were copied, no .env was generated, and no containers were started."
  exit 0
fi

if [ "$NO_START" != "1" ] || [ "$FORCE_REINSTALL" = "1" ]; then
  need_or_install_docker
fi

prepare_install_dir

if [ "$NO_START" != "1" ]; then
  check_public_ports_available
fi

if [ "$LOCAL_INSTALL" = "1" ]; then
  log "copying local tree"
  copy_local_tree
else
  log "downloading release"
  download_release
fi

render_env

if [ "$NO_START" = "1" ]; then
  echo "Lead Flow IA files are ready in $INSTALL_DIR"
  exit 0
fi

cd "$INSTALL_DIR"
log "pulling images"
if [ "$LOCAL_BUILD" = "1" ]; then
  log "building images locally"
  docker compose --env-file .env -f compose.prod.yml build langgraph-engine nextjs
elif [ "$SKIP_IMAGE_PULL" = "1" ]; then
  warn "SKIP_IMAGE_PULL=1; image pull skipped"
else
  docker compose --env-file .env -f compose.prod.yml pull
fi
log "starting dependencies"
docker compose --env-file .env -f compose.prod.yml up -d postgres redis minio langfuse
log "running migrations"
bash scripts/migrate.sh </dev/null
log "starting stack"
docker compose --env-file .env -f compose.prod.yml up -d
wait_for_nextjs_health
bash doctor.sh

echo
echo "Lead Flow IA installed: https://$DOMAIN"
if [ -n "$DOMAIN_ALIASES" ]; then
  echo "Aliases: $DOMAIN_ALIASES"
fi
echo "Open https://$DOMAIN/setup to create the first super admin account."
echo "Agent-native discovery: https://$DOMAIN/.well-known/leadflow-agent.json"
echo "CLI source: $INSTALL_DIR/packages/leadflow-cli"
echo "Environment: $INSTALL_DIR/.env"
