#!/usr/bin/env bash
set -euo pipefail

# Bump when changing this script’s behavior so support can correlate installs with the file revision.
INSTALLER_VERSION="1.6.0"

# Aida Controller — standalone install (no git clone, no compose files on disk).
# Pulls the host-controller image from ECR, lays out config under AIDA_INSTALL_DIR,
# starts host-controller, then calls POST /v1/bootstrap to pull the app stack and start services.
#
# Usage:
#   curl -fsSL https://aida-platform.com/controller/install.sh | sudo bash   # sudo required
#   sudo bash install.sh
#
# Environment (optional):
#   AIDA_INSTALL_DIR   default: /opt/aida-controller
#   ECR_REGISTRY       default: 643473958902.dkr.ecr.us-east-2.amazonaws.com/aida
#   HOST_CONTROLLER_TAG  default: latest  (ECR tag for aida-controller-host; synced into .env on each install)
#   RELEASE_TAG        default: latest  (app stack ECR tag; synced into .env on each install)
#   AWS_REGION         default: us-east-2
#   AIDA_CONTAINER_NAME  default: aida-host-controller
#   HC_PORT            default: 8001
#   AIDA_SKIP_DOCKER_INSTALL  default: unset. If Docker is missing on Ubuntu or Debian, install.sh adds Docker's apt repo
#                      and installs docker-ce + compose v2 (see https://docs.docker.com/engine/install/ubuntu/ ).
#                      Set to 1 to fail fast instead (install Docker yourself).
#   AIDA_ECR_ENV_FILE  default: ${AIDA_INSTALL_DIR}/.ecr-aws.env — if present, sourced for
#                      aws ecr get-login-password (host Docker) and passed into host-controller
#                      (boto3 ListImages). If missing, install.sh run from a git checkout uses
#                      host-controller/ecr-aws.env (copied into the install dir).
#   AIDA_SKIP_AWS_CLI_INSTALL  default: unset. If aws is missing, install AWS CLI v2 (official zip) when needed for ECR.
#                      Set to 1 to skip auto-install (you must provide aws on PATH).
#   AIDA_ECR_ENV_TEMPLATE_URL  optional. If ${AIDA_ECR_ENV_FILE} is missing, curl this URL to create the file before
#                      falling back to the embedded fleet ECR template (see _aida_write_ecr_env_placeholder_template).
#
# Fleet ECR credentials: the embedded template matches host-controller/ecr-aws.env (IAM limited to ECR pull/ListImages).
# Anyone who can read install.sh can use those keys — use least privilege and rotate if leaked. For stricter ops, omit
# embedded keys, host only AIDA_ECR_ENV_TEMPLATE_URL or per-host ${AIDA_ECR_ENV_FILE}.
#
# On success, writes under AIDA_INSTALL_DIR: start.sh, stop.sh, uninstall.sh (see below).
# When ${AIDA_ECR_ENV_FILE} is loaded, also writes ~/.aws/credentials + ~/.aws/config for root and SUDO_USER (mode 600).

AIDA_INSTALL_DIR="${AIDA_INSTALL_DIR:-/opt/aida-controller}"
AIDA_ECR_ENV_FILE="${AIDA_ECR_ENV_FILE:-${AIDA_INSTALL_DIR}/.ecr-aws.env}"

# Resolve bundled repo credentials when running: sudo bash install.sh from clone (not curl|bash).
_INSTALL_SCRIPT_DIR=""
if [[ -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then
  _INSTALL_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
fi
_BUNDLED_ECR_ENV="${_INSTALL_SCRIPT_DIR}/host-controller/ecr-aws.env"
if [[ ! -f "${AIDA_ECR_ENV_FILE}" ]] && [[ -f "${_BUNDLED_ECR_ENV}" ]]; then
  mkdir -p "${AIDA_INSTALL_DIR}"
  cp -a "${_BUNDLED_ECR_ENV}" "${AIDA_INSTALL_DIR}/.ecr-aws.env"
  chmod 600 "${AIDA_INSTALL_DIR}/.ecr-aws.env"
  AIDA_ECR_ENV_FILE="${AIDA_INSTALL_DIR}/.ecr-aws.env"
fi
ECR_REGISTRY="${ECR_REGISTRY:-643473958902.dkr.ecr.us-east-2.amazonaws.com/aida}"
HOST_CONTROLLER_TAG="${HOST_CONTROLLER_TAG:-latest}"
RELEASE_TAG="${RELEASE_TAG:-latest}"
AWS_REGION="${AWS_REGION:-us-east-2}"
AIDA_CONTAINER_NAME="${AIDA_CONTAINER_NAME:-aida-host-controller}"
HC_PORT="${HC_PORT:-8001}"

HC_IMAGE="${ECR_REGISTRY}/aida-controller-host:${HOST_CONTROLLER_TAG}"
ECR_HOST="${ECR_REGISTRY%%/*}"

# AWS CLI v2 — https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
_aida_install_aws_cli_v2() {
  if ! command -v curl &>/dev/null; then
    if command -v apt-get &>/dev/null; then
      export DEBIAN_FRONTEND=noninteractive
      apt-get update -qq
      apt-get install -y ca-certificates curl
    else
      echo "ERROR: curl is required to install AWS CLI."
      return 1
    fi
  fi
  if ! command -v unzip &>/dev/null; then
    if command -v apt-get &>/dev/null; then
      export DEBIAN_FRONTEND=noninteractive
      apt-get update -qq
      apt-get install -y unzip
    else
      echo "ERROR: unzip is required to install AWS CLI (install unzip or use apt-based OS)."
      return 1
    fi
  fi
  local zarch
  case "$(uname -m)" in
    x86_64) zarch=x86_64 ;;
    aarch64 | arm64) zarch=aarch64 ;;
    *)
      echo "ERROR: Unsupported architecture for AWS CLI v2: $(uname -m)"
      return 1
      ;;
  esac
  local tmp z
  tmp="$(mktemp -d)"
  z="${tmp}/awscliv2.zip"
  curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-${zarch}.zip" -o "${z}"
  unzip -q "${z}" -d "${tmp}"
  "${tmp}/aws/install" --update --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli
  rm -rf "${tmp}"
  command -v aws &>/dev/null
}

# Write shared credentials from environment (after sourcing AIDA_ECR_ENV_FILE).
_aida_write_aws_profiles_from_env() {
  [[ -n "${AWS_ACCESS_KEY_ID:-}" ]] || return 0
  [[ -n "${AWS_SECRET_ACCESS_KEY:-}" ]] || return 0
  if [[ "${AWS_ACCESS_KEY_ID}" == *REPLACE_ME* ]] || [[ "${AWS_SECRET_ACCESS_KEY}" == *REPLACE_ME* ]]; then
    return 0
  fi
  local region="${AWS_REGION:-${AWS_DEFAULT_REGION:-us-east-2}}"
  _aida_write_aws_dir "/root" root root "${region}"
  if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]] && id "$SUDO_USER" &>/dev/null; then
    local hd
    hd="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
    if [[ -n "$hd" ]]; then
      _aida_write_aws_dir "$hd" "$SUDO_USER" "$SUDO_USER" "${region}"
    fi
  fi
}

_aida_write_aws_dir() {
  local home="$1" owner="$2" group="$3" region="$4"
  install -d -m 700 "${home}/.aws"
  cat >"${home}/.aws/credentials" <<EOF
[default]
aws_access_key_id = ${AWS_ACCESS_KEY_ID}
aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}
EOF
  cat >"${home}/.aws/config" <<EOF
[default]
region = ${region}
output = json
EOF
  chmod 600 "${home}/.aws/credentials" "${home}/.aws/config"
  chown -R "${owner}:${group}" "${home}/.aws"
}

# Default ECR IAM env when curl|sudo bash (no repo copy). Keep in sync with host-controller/ecr-aws.env when rotating keys.
_aida_write_ecr_env_placeholder_template() {
  local path="$1"
  umask 077
  cat >"${path}" <<'EOF'
# Bundled ECR read credentials (IAM user: pull + ListImages on aida/aida-controller-*).
# Duplicated in install.sh for one-step curl|bash; also in host-controller/ecr-aws.env — rotate both when changing keys.
# Least-privilege policy; anyone with this script can pull from ECR.

AWS_ACCESS_KEY_ID=AKIAZLUQJM73GNI3PLVG
AWS_SECRET_ACCESS_KEY=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
AWS_DEFAULT_REGION=us-east-2
AWS_REGION=us-east-2
EOF
  chmod 600 "${path}"
}

# Install Docker Engine + Compose v2 from Docker's official apt repo (Ubuntu / Debian only).
# Mirrors: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
_aida_install_docker_apt() {
  local distro="$1" # ubuntu | debian
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y ca-certificates curl
  install -m 0755 -d /etc/apt/keyrings
  curl -fsSL "https://download.docker.com/linux/${distro}/gpg" -o /etc/apt/keyrings/docker.asc
  chmod a+r /etc/apt/keyrings/docker.asc
  # shellcheck source=/dev/null
  . /etc/os-release
  local suite="${VERSION_CODENAME:-}"
  if [[ "$distro" == ubuntu ]]; then
    suite="${UBUNTU_CODENAME:-$suite}"
  fi
  if [[ -z "$suite" ]]; then
    echo "ERROR: Could not determine ${distro} codename (VERSION_CODENAME) from /etc/os-release."
    return 1
  fi
  local arch
  arch="$(dpkg --print-architecture)"
  tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${distro}
Suites: ${suite}
Components: stable
Architectures: ${arch}
Signed-By: /etc/apt/keyrings/docker.asc
EOF
  apt-get update -qq
  apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  systemctl enable --now docker
}

_aida_try_install_docker() {
  if [[ ! -r /etc/os-release ]]; then
    echo "ERROR: Cannot read /etc/os-release; automatic Docker install is not available."
    return 1
  fi
  # shellcheck source=/dev/null
  . /etc/os-release
  case "${ID}" in
    ubuntu)
      echo "  Installing Docker Engine from Docker apt repository (Ubuntu)..."
      _aida_install_docker_apt ubuntu
      ;;
    debian)
      echo "  Installing Docker Engine from Docker apt repository (Debian)..."
      _aida_install_docker_apt debian
      ;;
    *)
      echo "ERROR: Docker is not installed. Automatic install supports Ubuntu and Debian only."
      echo "  Install Docker 24+ (https://docs.docker.com/engine/install/) then re-run this script,"
      echo "  or provision on Ubuntu/Debian Server."
      return 1
      ;;
  esac
}

# Self-signed TLS for Mosquitto listener 8883 (backend uses tls_insecure for LAN brokers).
_aida_gen_mqtt_certs() {
  local cert_dir="${1:?}"
  mkdir -p "${cert_dir}"
  if [[ -s "${cert_dir}/server.crt" && -s "${cert_dir}/server.key" && -s "${cert_dir}/ca.crt" ]]; then
    return 0
  fi
  if [[ -e "${cert_dir}/server.crt" || -e "${cert_dir}/server.key" || -e "${cert_dir}/ca.crt" ]]; then
    echo "  WARNING: Incomplete MQTT cert set in ${cert_dir}; fix or remove *.crt / *.key then re-run."
    return 0
  fi
  if ! command -v openssl &>/dev/null; then
    echo "  WARNING: openssl missing; skipping MQTT TLS cert generation (add certs manually for MQTTS 8883)."
    return 0
  fi
  local cn tmp
  cn="$(hostname -f 2>/dev/null || true)"
  if [[ -z "${cn}" || "${cn}" == "(none)" ]]; then
    cn="$(hostname 2>/dev/null || echo aida-controller)"
  fi
  tmp="$(mktemp -d)"
  openssl genrsa -out "${tmp}/ca.key" 2048
  openssl req -new -x509 -days 3650 -key "${tmp}/ca.key" -out "${cert_dir}/ca.crt" -subj "/CN=Aida-MQTT-CA"
  openssl genrsa -out "${cert_dir}/server.key" 2048
  openssl req -new -key "${cert_dir}/server.key" -out "${tmp}/server.csr" -subj "/CN=${cn}"
  openssl x509 -req -in "${tmp}/server.csr" -CA "${cert_dir}/ca.crt" -CAkey "${tmp}/ca.key" \
    -CAcreateserial -CAserial "${tmp}/mqtt-ca.srl" -out "${cert_dir}/server.crt" -days 825
  chmod 600 "${cert_dir}/server.key" 2>/dev/null || true
  chmod 644 "${cert_dir}/ca.crt" "${cert_dir}/server.crt" 2>/dev/null || true
  rm -rf "${tmp}"
  echo "  Generated self-signed MQTT TLS certs in ${cert_dir}"
  return 0
}

# Upsert KEY=value in the install .env (used so re-running install.sh refreshes RELEASE_TAG / HOST_CONTROLLER_TAG).
_aida_upsert_env_var() {
  local env_file="$1"
  local key="$2"
  local value="$3"
  local prefix="${key}="
  local out=()
  local found=0
  local line stripped
  while IFS= read -r line || [[ -n "$line" ]]; do
    stripped="${line#"${line%%[![:space:]]*}"}"
    if [[ "${stripped}" == "${prefix}"* && "${stripped}" != "#"* ]]; then
      out+=("${key}=${value}")
      found=1
    else
      out+=("${line}")
    fi
  done <"${env_file}"
  if [[ "${found}" -eq 0 ]]; then
    out+=("${key}=${value}")
  fi
  printf '%s\n' "${out[@]}" >"${env_file}.tmp"
  mv "${env_file}.tmp" "${env_file}"
}

# POST /v1/bootstrap can run a long time; some environments drop the HTTP connection (curl 52) even when compose succeeds.
# After the POST, always verify host-controller then nginx (port 80) /health for up to ~2 min each unless JSON reports success:false.
_aida_bootstrap_post() {
  local hc_port="$1"
  local tmp rc=0
  tmp="$(mktemp)"
  curl -sS --max-time 3600 -X POST "http://127.0.0.1:${hc_port}/v1/bootstrap" \
    -H "Content-Type: application/json" \
    -d "{\"release_tag\": \"${RELEASE_TAG}\"}" \
    -o "${tmp}" || rc=$?
  RESP="$(cat "${tmp}" 2>/dev/null || true)"
  rm -f "${tmp}"

  if echo "${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*false'; then
    return 1
  fi

  local json_ok=0
  if echo "${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*true'; then
    json_ok=1
  fi
  if [[ "${json_ok}" -eq 0 ]]; then
    echo "  Bootstrap HTTP incomplete or ambiguous (curl exit ${rc}). Verifying health endpoints..."
  fi

  echo "  Polling http://127.0.0.1:${hc_port}/health (up to ~2 min)..."
  local ok_hc=0 _i
  for _i in $(seq 1 60); do
    if curl -sf --max-time 5 "http://127.0.0.1:${hc_port}/health" &>/dev/null; then
      ok_hc=1
      break
    fi
    sleep 2
  done
  if [[ "${ok_hc}" -ne 1 ]]; then
    echo "  ERROR: host-controller /health did not become ready in time."
    return 1
  fi

  echo "  Polling http://127.0.0.1/health (up to ~2 min)..."
  local ok_80=0
  for _i in $(seq 1 60); do
    if curl -sf --max-time 5 "http://127.0.0.1/health" &>/dev/null; then
      ok_80=1
      break
    fi
    sleep 2
  done
  if [[ "${ok_80}" -ne 1 ]]; then
    echo "  ERROR: stack /health on port 80 did not become ready in time."
    return 1
  fi

  if [[ "${json_ok}" -eq 1 ]]; then
    return 0
  fi
  RESP='{"success":true,"detail":"Stack OK (bootstrap HTTP incomplete; verified host-controller and port 80 /health)."}'
  echo "  Stack verified — treating bootstrap as successful."
  return 0
}

# Writes start.sh, stop.sh, uninstall.sh under the install directory (paths embedded at install time).
_aida_write_operator_scripts() {
  local dir="$1"
  local hc="$2"
  local port="$3"
  install -d -m 755 "${dir}"

  cat >"${dir}/start.sh" <<EOF
#!/usr/bin/env bash
# Start host-controller (if stopped) and bring the application stack up (same as install bootstrap).
# Requires: Docker, curl. Run with sudo if your Docker socket requires it.
set -euo pipefail
HC="${hc}"
PORT="${port}"
if ! docker info &>/dev/null; then
  echo "ERROR: Cannot talk to Docker (try: sudo \$0)"
  exit 1
fi
if ! docker inspect "\${HC}" &>/dev/null; then
  echo "ERROR: Container \${HC} not found. Run install.sh first."
  exit 1
fi
if [[ "\$(docker inspect -f '{{.State.Running}}' "\${HC}" 2>/dev/null)" != "true" ]]; then
  echo "Starting host-controller..."
  docker start "\${HC}"
fi
echo "Waiting for host-controller..."
for _ in \$(seq 1 60); do
  if curl -sf "http://127.0.0.1:\${PORT}/health" &>/dev/null; then
    break
  fi
  sleep 1
done
if ! curl -sf "http://127.0.0.1:\${PORT}/health" &>/dev/null; then
  echo "ERROR: host-controller unhealthy. docker logs \${HC}"
  exit 1
fi
echo "Bootstrapping / reconciling stack..."
BTMP=\$(mktemp)
BC=0
curl -sS --max-time 3600 -X POST "http://127.0.0.1:\${PORT}/v1/bootstrap" \\
  -H "Content-Type: application/json" \\
  -d '{"release_tag": null}' \\
  -o "\${BTMP}" || BC=\$?
RESP=\$(cat "\${BTMP}" 2>/dev/null || true)
rm -f "\${BTMP}"
if echo "\${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*false'; then
  echo "\${RESP}" | head -c 2000
  echo ""
  echo "ERROR: bootstrap reported failure. docker logs \${HC}"
  exit 1
fi
JSON_OK=0
if echo "\${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*true'; then
  JSON_OK=1
fi
if [[ "\${JSON_OK}" -eq 0 ]]; then
  echo "  Bootstrap HTTP incomplete or ambiguous (curl exit \${BC}). Verifying health endpoints..."
fi
echo "  Polling http://127.0.0.1:\${PORT}/health (up to ~2 min)..."
OK_HC=0
for _ in \$(seq 1 60); do
  if curl -sf --max-time 5 "http://127.0.0.1:\${PORT}/health" &>/dev/null; then
    OK_HC=1
    break
  fi
  sleep 2
done
if [[ "\${OK_HC}" -ne 1 ]]; then
  echo "ERROR: host-controller /health did not become ready. docker logs \${HC}"
  exit 1
fi
echo "  Polling http://127.0.0.1/health (up to ~2 min)..."
OK_80=0
for _ in \$(seq 1 60); do
  if curl -sf --max-time 5 "http://127.0.0.1/health" &>/dev/null; then
    OK_80=1
    break
  fi
  sleep 2
done
if [[ "\${OK_80}" -ne 1 ]]; then
  echo "ERROR: stack /health on port 80 did not become ready."
  exit 1
fi
if [[ "\${JSON_OK}" -eq 0 ]]; then
  RESP='{"success":true,"detail":"Stack OK (bootstrap HTTP incomplete; verified host-controller and port 80 /health)."}'
  echo "  Stack verified — treating bootstrap as successful."
fi
echo "\${RESP}" | head -c 2000
echo ""
if echo "\${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*true'; then
  echo "Started."
else
  echo "ERROR: bootstrap failed. docker logs \${HC}"
  exit 1
fi
EOF
  chmod 755 "${dir}/start.sh"

  cat >"${dir}/stop.sh" <<EOF
#!/usr/bin/env bash
# Stop application containers (compose stop) via host-controller; leaves host-controller running.
set -euo pipefail
HC="${hc}"
if ! docker info &>/dev/null; then
  echo "ERROR: Cannot talk to Docker (try: sudo \$0)"
  exit 1
fi
if ! docker inspect "\${HC}" &>/dev/null; then
  echo "Host-controller not present; nothing to stop."
  exit 0
fi
if [[ "\$(docker inspect -f '{{.State.Running}}' "\${HC}" 2>/dev/null)" != "true" ]]; then
  echo "Host-controller not running."
  exit 0
fi
docker exec "\${HC}" docker compose --project-directory /workspace \\
  -f /app/stack/docker-compose.prod.yml \\
  -f /app/stack/docker-compose.prod.ecr.yml \\
  stop
echo "Stack stopped (host-controller still running)."
EOF
  chmod 755 "${dir}/stop.sh"

  cat >"${dir}/uninstall.sh" <<'UNINSTALLEOF'
#!/usr/bin/env bash
# Remove application stack and host-controller. Default keeps Docker volumes (DB data).
# Usage: sudo ./uninstall.sh  |  sudo ./uninstall.sh --purge-volumes
set -euo pipefail
PURGE=0
for arg in "$@"; do
  case "$arg" in
    --purge-volumes) PURGE=1 ;;
    -h|--help)
      echo "Usage: $0 [--purge-volumes]   # --purge-volumes runs compose down -v (deletes DB/redis volumes)"
      exit 0
      ;;
    *) echo "Unknown option: $arg"; exit 1 ;;
  esac
done
UNINSTALLEOF

  cat >>"${dir}/uninstall.sh" <<EOF
HC="${hc}"
if ! docker info &>/dev/null; then
  echo "ERROR: Cannot talk to Docker (try: sudo \$0)"
  exit 1
fi
if docker inspect "\${HC}" &>/dev/null; then
  if [[ "\$(docker inspect -f '{{.State.Running}}' "\${HC}" 2>/dev/null)" != "true" ]]; then
    docker start "\${HC}" || true
    sleep 2
  fi
  if [[ "\$(docker inspect -f '{{.State.Running}}' "\${HC}" 2>/dev/null)" == "true" ]]; then
    if [[ "\${PURGE}" == "1" ]]; then
      docker exec "\${HC}" docker compose --project-directory /workspace \\
        -f /app/stack/docker-compose.prod.yml \\
        -f /app/stack/docker-compose.prod.ecr.yml \\
        down -v
    else
      docker exec "\${HC}" docker compose --project-directory /workspace \\
        -f /app/stack/docker-compose.prod.yml \\
        -f /app/stack/docker-compose.prod.ecr.yml \\
        down
    fi
  fi
  docker rm -f "\${HC}" 2>/dev/null || true
fi
echo "Host-controller removed."
echo "Install directory left at: ${dir}  (remove manually if desired: rm -rf ${dir})"
EOF
  chmod 755 "${dir}/uninstall.sh"
}

for arg in "$@"; do
  case "$arg" in
    --help|-h)
      echo "Usage: $0"
      echo "  INSTALLER_VERSION=${INSTALLER_VERSION}"
      echo "  Standalone install: creates ${AIDA_INSTALL_DIR}, runs host-controller, POST /v1/bootstrap"
      echo "  See script header for environment variables."
      exit 0
      ;;
  esac
done

echo "============================================="
echo "  Aida Controller — standalone install"
echo "============================================="
echo "  Installer:       ${INSTALLER_VERSION}"
echo "  Install dir:     ${AIDA_INSTALL_DIR}"
echo "  Host-controller: ${HC_IMAGE}"
echo "  App images tag:  ${RELEASE_TAG}"
echo ""

if [[ "$(id -u)" -ne 0 ]]; then
  echo "ERROR: Run as root (sudo) so we can create ${AIDA_INSTALL_DIR} and run Docker."
  exit 1
fi

echo "[1/7] Checking prerequisites..."
if ! command -v docker &>/dev/null; then
  if [[ "${AIDA_SKIP_DOCKER_INSTALL:-0}" == "1" ]]; then
    echo "ERROR: Docker is not installed. Install Docker 24+ and try again, or omit AIDA_SKIP_DOCKER_INSTALL to auto-install on Ubuntu/Debian."
    exit 1
  fi
  if ! _aida_try_install_docker; then
    exit 1
  fi
  if ! command -v docker &>/dev/null; then
    echo "ERROR: Docker install finished but docker is not on PATH."
    exit 1
  fi
  echo "  Docker installed."
fi
if ! docker compose version &>/dev/null; then
  echo "ERROR: Docker Compose V2 is required."
  exit 1
fi
if ! command -v openssl &>/dev/null; then
  echo "ERROR: openssl is required to generate secrets."
  exit 1
fi
# Non-root invoker (sudo): add to docker group so future shells can use docker without sudo.
# Group membership applies after a new login session; `newgrp docker` works in the current shell only.
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
  if id "$SUDO_USER" &>/dev/null && getent group docker &>/dev/null; then
    if id -nG "$SUDO_USER" | grep -qw docker; then
      echo "  ${SUDO_USER} already in group docker"
    else
      usermod -aG docker "$SUDO_USER"
      echo "  Added ${SUDO_USER} to group docker — use a new SSH login or run: newgrp docker"
    fi
  fi
fi
echo "  Docker OK"
echo ""

echo "[2/7] Preparing install directory..."
mkdir -p "${AIDA_INSTALL_DIR}/.aida"
chmod 755 "${AIDA_INSTALL_DIR}" || true
if [[ -f "${AIDA_ECR_ENV_FILE}" ]]; then
  chmod 600 "${AIDA_ECR_ENV_FILE}" 2>/dev/null || true
fi

# curl | sudo bash cannot copy bundled host-controller/ecr-aws.env (script is stdin). Ensure file exists or exit with instructions.
if [[ ! -f "${AIDA_ECR_ENV_FILE}" ]]; then
  if [[ -n "${AWS_ACCESS_KEY_ID:-}" && -n "${AWS_SECRET_ACCESS_KEY:-}" \
        && "${AWS_ACCESS_KEY_ID}" != *REPLACE_ME* && "${AWS_SECRET_ACCESS_KEY}" != *REPLACE_ME* ]]; then
    umask 077
    _r="${AWS_REGION:-${AWS_DEFAULT_REGION:-us-east-2}}"
    {
      printf '%s\n' "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}"
      printf '%s\n' "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}"
      printf '%s\n' "AWS_DEFAULT_REGION=${_r}"
      printf '%s\n' "AWS_REGION=${_r}"
    } >"${AIDA_ECR_ENV_FILE}"
    chmod 600 "${AIDA_ECR_ENV_FILE}"
    echo "  Created ${AIDA_ECR_ENV_FILE} from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (e.g. sudo -E bash …)."
  elif [[ -n "${AIDA_ECR_ENV_TEMPLATE_URL:-}" ]] && command -v curl &>/dev/null; then
    echo "  Downloading ECR env template from AIDA_ECR_ENV_TEMPLATE_URL..."
    if curl -fsSL "${AIDA_ECR_ENV_TEMPLATE_URL}" -o "${AIDA_ECR_ENV_FILE}.tmp"; then
      mv "${AIDA_ECR_ENV_FILE}.tmp" "${AIDA_ECR_ENV_FILE}"
      chmod 600 "${AIDA_ECR_ENV_FILE}"
      echo "  Wrote ${AIDA_ECR_ENV_FILE}"
    else
      rm -f "${AIDA_ECR_ENV_FILE}.tmp"
      _aida_write_ecr_env_placeholder_template "${AIDA_ECR_ENV_FILE}"
      echo "  Template URL failed; wrote embedded fleet ECR credentials to ${AIDA_ECR_ENV_FILE}"
    fi
  else
    _aida_write_ecr_env_placeholder_template "${AIDA_ECR_ENV_FILE}"
    echo "  Wrote ${AIDA_ECR_ENV_FILE} (embedded fleet ECR credentials for one-step install)."
  fi
  if [[ "${AWS_ACCESS_KEY_ID:-}" == *REPLACE_ME* ]] || [[ "${AWS_SECRET_ACCESS_KEY:-}" == *REPLACE_ME* ]] \
      || ! grep -q '^AWS_ACCESS_KEY_ID=.' "${AIDA_ECR_ENV_FILE}" 2>/dev/null \
      || grep -q '^AWS_ACCESS_KEY_ID=REPLACE_ME' "${AIDA_ECR_ENV_FILE}" 2>/dev/null \
      || grep -q '^AWS_SECRET_ACCESS_KEY=REPLACE_ME' "${AIDA_ECR_ENV_FILE}" 2>/dev/null; then
    echo ""
    echo "ERROR: ${AIDA_ECR_ENV_FILE} needs real IAM keys before ECR images can be pulled."
    echo "  Edit that file on this host (sudo nano ${AIDA_ECR_ENV_FILE}), replace both REPLACE_ME values, save, then re-run:"
    echo "    curl -fsSL https://aida-platform.com/controller/install.sh | sudo bash"
    echo "  Or pass credentials once (not logged in shell history if you use a wrapper):"
    echo "    export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_REGION=us-east-2"
    echo "    curl -fsSL https://aida-platform.com/controller/install.sh | sudo -E bash"
    exit 1
  fi
fi
if [[ -f "${AIDA_ECR_ENV_FILE}" ]]; then
  chmod 600 "${AIDA_ECR_ENV_FILE}" 2>/dev/null || true
fi
echo ""

if [[ ! -f "${AIDA_INSTALL_DIR}/.env" ]]; then
  echo "[3/7] Creating ${AIDA_INSTALL_DIR}/.env ..."
  SECRET_KEY=$(openssl rand -hex 32)
  DB_PASSWORD=$(openssl rand -hex 16)
  cat > "${AIDA_INSTALL_DIR}/.env" <<EOF
ENVIRONMENT=production
ECR_REGISTRY=${ECR_REGISTRY}
RELEASE_TAG=${RELEASE_TAG}
HOST_CONTROLLER_TAG=${HOST_CONTROLLER_TAG}
HC_PORT=${HC_PORT}
SECRET_KEY=${SECRET_KEY}
DB_PASSWORD=${DB_PASSWORD}
CORS_ORIGINS=http://localhost
# Host paths for bind mounts (compose runs inside aida-host-controller; relative paths break on the Docker host).
NGINX_VOLUME_SRC=${AIDA_INSTALL_DIR}/nginx
MOSQUITTO_VOLUME_SRC=${AIDA_INSTALL_DIR}/mosquitto
AIDA_SSL_APPLY_MQTT=1
EOF
  echo "  Generated .env with random SECRET_KEY and DB_PASSWORD"

else
  echo "[3/7] Using existing ${AIDA_INSTALL_DIR}/.env"
  # Ensure ECR/registry keys exist for compose
  if ! grep -q '^ECR_REGISTRY=' "${AIDA_INSTALL_DIR}/.env" 2>/dev/null; then
    echo "ECR_REGISTRY=${ECR_REGISTRY}" >> "${AIDA_INSTALL_DIR}/.env"
  fi
  if ! grep -q '^RELEASE_TAG=' "${AIDA_INSTALL_DIR}/.env" 2>/dev/null; then
    echo "RELEASE_TAG=${RELEASE_TAG}" >> "${AIDA_INSTALL_DIR}/.env"
  fi
  if ! grep -q '^NGINX_VOLUME_SRC=' "${AIDA_INSTALL_DIR}/.env" 2>/dev/null; then
    echo "NGINX_VOLUME_SRC=${AIDA_INSTALL_DIR}/nginx" >> "${AIDA_INSTALL_DIR}/.env"
  fi
  if ! grep -q '^MOSQUITTO_VOLUME_SRC=' "${AIDA_INSTALL_DIR}/.env" 2>/dev/null; then
    echo "MOSQUITTO_VOLUME_SRC=${AIDA_INSTALL_DIR}/mosquitto" >> "${AIDA_INSTALL_DIR}/.env"
  fi
  if ! grep -q '^AIDA_SSL_APPLY_MQTT=' "${AIDA_INSTALL_DIR}/.env" 2>/dev/null; then
    echo "AIDA_SSL_APPLY_MQTT=1" >> "${AIDA_INSTALL_DIR}/.env"
  fi
  # docker-compose.prod forces ENVIRONMENT=production for backend/celery; default SECRET_KEY fails validation.
  _aida_env="${AIDA_INSTALL_DIR}/.env"
  _aida_sk_val=""
  if grep -qE '^[[:space:]]*SECRET_KEY=' "${_aida_env}" 2>/dev/null; then
    _aida_sk_val="$(grep -E '^[[:space:]]*SECRET_KEY=' "${_aida_env}" | tail -1 | sed -E 's/^[[:space:]]*SECRET_KEY=//; s/^["'\'']//; s/["'\'']$//; s/\r$//')"
  fi
  if [[ -z "${_aida_sk_val}" || "${_aida_sk_val}" == "change-me-in-production" ]]; then
    _aida_new_sk="$(openssl rand -hex 32)"
    sed -i.bak '/^[[:space:]]*SECRET_KEY=/d' "${_aida_env}"
    printf 'SECRET_KEY=%s\n' "${_aida_new_sk}" >> "${_aida_env}"
    rm -f "${_aida_env}.bak"
    chmod 600 "${_aida_env}" 2>/dev/null || true
    echo "  Wrote new SECRET_KEY (production stack requires a non-default secret; was missing or placeholder)."
  fi
  _aida_upsert_env_var "${AIDA_INSTALL_DIR}/.env" "ECR_REGISTRY" "${ECR_REGISTRY}"
  _aida_upsert_env_var "${AIDA_INSTALL_DIR}/.env" "RELEASE_TAG" "${RELEASE_TAG}"
  _aida_upsert_env_var "${AIDA_INSTALL_DIR}/.env" "HOST_CONTROLLER_TAG" "${HOST_CONTROLLER_TAG}"
  _aida_upsert_env_var "${AIDA_INSTALL_DIR}/.env" "HC_PORT" "${HC_PORT}"
  echo "  Synced RELEASE_TAG=${RELEASE_TAG} HOST_CONTROLLER_TAG=${HOST_CONTROLLER_TAG} in .env"
fi
echo ""

echo "[4/7] ECR login..."
AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-us-east-2}}"
export AWS_REGION

if [[ -f "${AIDA_ECR_ENV_FILE}" ]]; then
  echo "  Loading AWS credentials from ${AIDA_ECR_ENV_FILE}"
  set -a
  # shellcheck source=/dev/null
  source "${AIDA_ECR_ENV_FILE}"
  set +a
  AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-us-east-2}}"
  export AWS_REGION
  if [[ "${AWS_ACCESS_KEY_ID:-}" == *REPLACE_ME* ]] || [[ "${AWS_SECRET_ACCESS_KEY:-}" == *REPLACE_ME* ]]; then
    echo "ERROR: ${AIDA_ECR_ENV_FILE} still contains REPLACE_ME placeholders."
    echo "  Edit real IAM keys into ${AIDA_INSTALL_DIR}/.ecr-aws.env (or host-controller/ecr-aws.env in a checkout), then reinstall."
    exit 1
  fi

  if [[ "${AIDA_SKIP_AWS_CLI_INSTALL:-0}" != "1" ]] && ! command -v aws &>/dev/null; then
    echo "  Installing AWS CLI v2..."
    if ! _aida_install_aws_cli_v2; then
      echo "ERROR: AWS CLI installation failed."
      exit 1
    fi
    echo "  AWS CLI ready."
  fi

  if ! command -v aws &>/dev/null; then
    echo "ERROR: aws is not on PATH (do not set AIDA_SKIP_AWS_CLI_INSTALL=1 unless aws is preinstalled, or install aws manually)."
    exit 1
  fi

  _aida_write_aws_profiles_from_env
  if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
    echo "  Wrote ~/.aws/credentials and ~/.aws/config for root and ${SUDO_USER}."
  else
    echo "  Wrote ~/.aws/credentials and ~/.aws/config for root."
  fi

  _ecr_err="$(mktemp)"
  _dkr_err="$(mktemp)"
  if aws ecr get-login-password --region "${AWS_REGION}" 2>"${_ecr_err}" | docker login --username AWS --password-stdin "${ECR_HOST}" 2>"${_dkr_err}"; then
    echo "  Logged in to ECR."
    rm -f "${_ecr_err}" "${_dkr_err}"
  else
    echo "ERROR: ECR docker login failed. Check IAM (ecr:GetAuthorizationToken; pull on ${ECR_REGISTRY}) and region ${AWS_REGION}."
    [[ -s "${_ecr_err}" ]] && echo "  aws:" && sed 's/^/    /' "${_ecr_err}"
    [[ -s "${_dkr_err}" ]] && echo "  docker:" && sed 's/^/    /' "${_dkr_err}"
    rm -f "${_ecr_err}" "${_dkr_err}"
    exit 1
  fi
else
  echo "  No ${AIDA_ECR_ENV_FILE} — private ECR pulls need Docker already logged in or this file with AWS keys."
  if [[ "${AIDA_SKIP_AWS_CLI_INSTALL:-0}" != "1" ]] && ! command -v aws &>/dev/null && command -v apt-get &>/dev/null; then
    echo "  Installing AWS CLI v2 (optional)..."
    _aida_install_aws_cli_v2 || echo "  WARNING: AWS CLI install failed; skipped."
  fi
  if command -v aws &>/dev/null; then
    if aws ecr get-login-password --region "${AWS_REGION}" 2>/dev/null | docker login --username AWS --password-stdin "${ECR_HOST}" &>/dev/null; then
      echo "  Logged in to ECR (default AWS credential chain)."
    else
      echo "  WARNING: ECR login failed. Add ${AIDA_INSTALL_DIR}/.ecr-aws.env or configure ~/.aws and re-run."
    fi
  fi
fi
echo ""

echo "[5/7] Pulling host-controller image and extracting nginx config template..."
docker pull "${HC_IMAGE}"

docker run --rm \
  -v "${AIDA_INSTALL_DIR}:/out" \
  "${HC_IMAGE}" \
  sh -c 'set -e
    if [ ! -d /out/nginx ] || [ -z "$(ls -A /out/nginx 2>/dev/null)" ]; then cp -a /app/stack/nginx /out/; fi
    if [ -f /app/stack/mosquitto/config/mosquitto.conf ]; then
      mkdir -p /out/mosquitto/config /out/mosquitto/data /out/mosquitto/logs /out/mosquitto/certs
      if [ ! -f /out/mosquitto/config/mosquitto.conf ]; then
        cp /app/stack/mosquitto/config/mosquitto.conf /out/mosquitto/config/
      fi
    fi
  '

echo "  nginx layout: ${AIDA_INSTALL_DIR}/nginx"
mkdir -p "${AIDA_INSTALL_DIR}/ssl"
chmod 700 "${AIDA_INSTALL_DIR}/ssl" 2>/dev/null || true
mkdir -p "${AIDA_INSTALL_DIR}/install-scripts"
if [[ -f "${_INSTALL_SCRIPT_DIR}/install-scripts/apply-custom-ssl.sh" ]]; then
  cp -f "${_INSTALL_SCRIPT_DIR}/install-scripts/apply-custom-ssl.sh" "${AIDA_INSTALL_DIR}/install-scripts/"
  chmod 755 "${AIDA_INSTALL_DIR}/install-scripts/apply-custom-ssl.sh"
fi
_aida_gen_mqtt_certs "${AIDA_INSTALL_DIR}/mosquitto/certs"
echo "  mosquitto layout: ${AIDA_INSTALL_DIR}/mosquitto/"
echo ""

_aida_write_operator_scripts "${AIDA_INSTALL_DIR}" "${AIDA_CONTAINER_NAME}" "${HC_PORT}"

echo "[6/7] Stopping any existing application stack, then starting host-controller container..."
if docker inspect "${AIDA_CONTAINER_NAME}" &>/dev/null; then
  if [[ "$(docker inspect -f '{{.State.Running}}' "${AIDA_CONTAINER_NAME}" 2>/dev/null)" != "true" ]]; then
    echo "  Starting existing host-controller so stop.sh can reach Docker via compose..."
    docker start "${AIDA_CONTAINER_NAME}" || true
    for _ in $(seq 1 30); do
      if docker exec "${AIDA_CONTAINER_NAME}" docker info &>/dev/null; then
        break
      fi
      sleep 1
    done
  fi
fi
if [[ -x "${AIDA_INSTALL_DIR}/stop.sh" ]]; then
  bash "${AIDA_INSTALL_DIR}/stop.sh" || true
fi

docker rm -f "${AIDA_CONTAINER_NAME}" 2>/dev/null || true
HC_ENV_ARGS=()
if [[ -f "${AIDA_ECR_ENV_FILE}" ]]; then
  HC_ENV_ARGS+=(--env-file "${AIDA_ECR_ENV_FILE}")
fi
docker run -d \
  --name "${AIDA_CONTAINER_NAME}" \
  --restart always \
  --add-host=host.docker.internal:host-gateway \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "${AIDA_INSTALL_DIR}:/workspace" \
  -v "${AIDA_INSTALL_DIR}/.aida:/data" \
  -v /etc/environment:/etc/environment:ro \
  -e HC_PROJECT_DIR=/workspace \
  -e HC_HOST_PROJECT_DIR="${AIDA_INSTALL_DIR}" \
  -e HC_STATE_FILE=/data/state.json \
  -e HC_HOST_CONTAINER_NAME="${AIDA_CONTAINER_NAME}" \
  -e HC_HEALTHCHECK_URL=http://host.docker.internal/health \
  -p "127.0.0.1:${HC_PORT}:8001" \
  "${HC_ENV_ARGS[@]}" \
  "${HC_IMAGE}"

echo "  Waiting for host-controller health..."
for _ in $(seq 1 60); do
  if curl -sf "http://127.0.0.1:${HC_PORT}/health" &>/dev/null; then
    break
  fi
  sleep 1
done
if ! curl -sf "http://127.0.0.1:${HC_PORT}/health" &>/dev/null; then
  echo "ERROR: host-controller did not become healthy."
  docker logs "${AIDA_CONTAINER_NAME}" 2>&1 | tail -50
  exit 1
fi
echo ""

echo "[7/7] Bootstrapping full stack (pull images, migrations, services)..."
# Bootstrap can take many minutes. If the TCP connection drops before JSON (curl 52), we verify port 80 /health.
if ! _aida_bootstrap_post "${HC_PORT}"; then
  echo "${RESP}" | head -c 2000
  echo ""
  echo "ERROR: Bootstrap reported failure. Check: docker logs ${AIDA_CONTAINER_NAME}"
  exit 1
fi

echo "${RESP}" | head -c 2000
echo ""

if echo "${RESP}" | grep -qE '"success"[[:space:]]*:[[:space:]]*true'; then
  echo ""
  echo "============================================="
  echo "  Install finished."
  echo "============================================="
  echo "  Installer:       ${INSTALLER_VERSION}"
  echo "  Open http://localhost (port 80) on this host."
  echo "  Host-controller API: http://127.0.0.1:${HC_PORT}/docs"
  echo "  If an admin password was printed in JSON, use user: admin"
  echo "  Operator scripts: ${AIDA_INSTALL_DIR}/start.sh | stop.sh | uninstall.sh"
  echo ""
else
  echo "ERROR: Bootstrap reported failure. Check: docker logs ${AIDA_CONTAINER_NAME}"
  exit 1
fi
