#!/usr/bin/env bash
# =============================================================================
# Changemaker Lite — One-Line Installer
#
# Downloads the latest release tarball and runs the configuration wizard.
#
# Usage:
#   curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/main/scripts/install.sh | bash
#   bash install.sh [OPTIONS]
#
# Options:
#   --dir DIR       Install directory (default: ~/changemaker.lite)
#   --version TAG   Specific version tag (default: latest release)
#   --tarball FILE  Use a local tarball instead of downloading
#   --help          Show this help
# =============================================================================
set -euo pipefail

GITEA_URL="https://gitea.bnkops.com"
REPO="admin/changemaker.lite"
INSTALL_DIR="${HOME}/changemaker.lite"
VERSION=""
LOCAL_TARBALL=""
MIN_DISK_MB=10000
HEALTH_TIMEOUT=180
HEALTH_INTERVAL=5

# --- State flags for cleanup ---
TARBALL_PATH=""
EXTRACT_COMPLETE=false
CONFIG_COMPLETE=false

# --- Colors ---
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
  RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
  BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m'
else
  RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
fi

info()    { echo -e "${BLUE}[INFO]${NC}  $*"; }
success() { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*" >&2; }

# --- Cleanup on failure ---
cleanup() {
  local exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    # Clean up downloaded tarball (but not user-provided ones)
    if [[ -z "$LOCAL_TARBALL" ]] && [[ -n "$TARBALL_PATH" ]] && [[ -f "$TARBALL_PATH" ]]; then
      rm -f "$TARBALL_PATH"
    fi

    # Post-extract state: tarball unpacked OK but config wizard didn't complete
    # (common case: /dev/tty unavailable over non-interactive SSH). The expensive
    # step succeeded, so preserve the dir and tell the user how to resume instead
    # of forcing a full re-download.
    if [[ "$EXTRACT_COMPLETE" == "true" ]] \
        && [[ "$CONFIG_COMPLETE" == "false" ]] \
        && [[ -d "$INSTALL_DIR" ]] \
        && [[ ! -f "$INSTALL_DIR/.env" ]]; then
      echo ""
      echo -e "${YELLOW}[INFO]${NC}  Tarball extracted to $INSTALL_DIR — preserving."
      echo -e "${YELLOW}[INFO]${NC}  To finish setup on an interactive console:"
      echo -e "           ${BOLD}cd $INSTALL_DIR && bash config.sh${NC}"
      echo ""
      error "Configuration wizard could not run (likely no TTY available)."
      return 0
    fi

    # Extraction failed or never happened — discard the partial dir so the next
    # run-through starts from a clean slate.
    if [[ "$CONFIG_COMPLETE" == "false" ]] && [[ -d "$INSTALL_DIR" ]] && [[ ! -f "$INSTALL_DIR/.env" ]]; then
      rm -rf "$INSTALL_DIR"
    fi
    echo ""
    error "Installation failed. See errors above."
  fi
}
trap cleanup EXIT

# --- Arg parser ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --dir)      INSTALL_DIR="$2"; shift 2 ;;
    --version)  VERSION="$2"; shift 2 ;;
    --tarball)  LOCAL_TARBALL="$2"; shift 2 ;;
    --help|-h)
      sed -n '2,20p' "$0" | grep '^#' | sed 's/^# \?//'
      exit 0 ;;
    *) shift ;;
  esac
done

echo -e "${BOLD}Changemaker Lite — Installer${NC}"
echo ""

# =============================================================================
# Step 1: Check prerequisites
# =============================================================================

info "Checking prerequisites..."
MISSING=()
command -v docker    >/dev/null 2>&1 || MISSING+=("docker")
docker compose version >/dev/null 2>&1 || MISSING+=("docker-compose-v2")
command -v openssl   >/dev/null 2>&1 || MISSING+=("openssl")
command -v curl      >/dev/null 2>&1 || MISSING+=("curl")

if [[ ${#MISSING[@]} -gt 0 ]]; then
  error "Missing required tools: ${MISSING[*]}"
  echo ""
  echo "Install Docker:  https://docs.docker.com/engine/install/"
  echo "Install OpenSSL: apt install openssl  (or equivalent)"
  exit 1
fi
success "Prerequisites OK (Docker $(docker --version | grep -oP '\d+\.\d+\.\d+'), OpenSSL available)"

# Docker daemon must be running (not just installed)
if ! docker info >/dev/null 2>&1; then
  error "Docker daemon is not running."
  echo ""
  echo "  Start it with:  sudo systemctl start docker"
  echo "  Or:             sudo service docker start"
  exit 1
fi
success "Docker daemon is running"

# Disk space check
AVAILABLE_MB=$(df -m "$(dirname "$INSTALL_DIR")" | awk 'NR==2 {print $4}')
if [[ "$AVAILABLE_MB" -lt "$MIN_DISK_MB" ]]; then
  error "Insufficient disk space: ${AVAILABLE_MB}MB available, ${MIN_DISK_MB}MB required."
  echo ""
  echo "  The full stack (images + volumes) needs ~10GB of free space."
  exit 1
fi
success "Disk space: ${AVAILABLE_MB}MB available (${MIN_DISK_MB}MB required)"

# Host port availability — checks the ports the stack will try to bind BEFORE
# we've downloaded anything. Avoids partially-installed state when e.g. cockpit
# owns :9090 and breaks prometheus mid-startup.
if command -v ss >/dev/null 2>&1; then
  HOST_CONFLICTS=()
  for port in 3000 4000 4100 5433 9090 3001 3030 9001 5678 8091 8025 8888 3010 4003; do
    if ss -Htln 2>/dev/null | awk -v p=":$port" '$4 ~ p"$" {found=1} END{exit !found}'; then
      HOST_CONFLICTS+=("$port")
    fi
  done
  if [[ ${#HOST_CONFLICTS[@]} -gt 0 ]]; then
    error "Host ports already in use: ${HOST_CONFLICTS[*]}"
    echo ""
    echo "  These ports must be free for the Changemaker Lite stack:"
    for p in "${HOST_CONFLICTS[@]}"; do
      case "$p" in
        9090) echo "    :$p  — commonly cockpit.socket. Fix: sudo systemctl disable --now cockpit.socket" ;;
        80|443) echo "    :$p  — host nginx/apache. Stop the host service or use a different subdomain entrypoint." ;;
        3030) echo "    :$p  — another Gitea or service on this port." ;;
        *) echo "    :$p" ;;
      esac
    done
    echo ""
    echo "  Or re-run later with --dir to install into a directory whose stack remaps ports."
    exit 1
  fi
  success "Host ports available"
else
  warn "ss not installed — skipping host port check (apt install iproute2 to enable)"
fi

# =============================================================================
# Step 2: Check install directory
# =============================================================================

if [[ -d "$INSTALL_DIR" ]]; then
  if [[ -f "$INSTALL_DIR/docker-compose.yml" ]]; then
    error "Changemaker Lite is already installed at $INSTALL_DIR"
    echo "  To upgrade: cd $INSTALL_DIR && ./scripts/upgrade.sh"
    echo "  To reinstall: rm -rf $INSTALL_DIR && re-run this script"
    exit 1
  fi
fi

# =============================================================================
# Step 3: Get tarball
# =============================================================================

if [[ -n "$LOCAL_TARBALL" ]]; then
  if [[ ! -f "$LOCAL_TARBALL" ]]; then
    error "Tarball not found: $LOCAL_TARBALL"
    exit 1
  fi
  TARBALL_PATH="$LOCAL_TARBALL"
  info "Using local tarball: $LOCAL_TARBALL"
else
  # Determine download URL
  if [[ -n "$VERSION" ]]; then
    RELEASE_URL="${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${VERSION}"
  else
    RELEASE_URL="${GITEA_URL}/api/v1/repos/${REPO}/releases/latest"
  fi

  info "Fetching release info from Gitea..."
  RELEASE_JSON=$(curl -sf "$RELEASE_URL" 2>/dev/null || true)

  if [[ -z "$RELEASE_JSON" ]]; then
    error "Could not fetch release info from ${GITEA_URL}"
    echo ""
    echo "If the server is unreachable:"
    echo "  1. Download the tarball manually from ${GITEA_URL}/${REPO}/releases"
    echo "  2. Run: bash install.sh --tarball /path/to/changemaker-lite-*.tar.gz"
    exit 1
  fi

  TARBALL_URL=$(echo "$RELEASE_JSON" | python3 -c "
import sys, json
data = json.load(sys.stdin)
assets = data.get('assets', [])
for a in assets:
    if a['name'].endswith('.tar.gz'):
        print(a['browser_download_url'])
        break
" 2>/dev/null || true)

  if [[ -z "$TARBALL_URL" ]]; then
    error "No tarball found in the release. Check ${GITEA_URL}/${REPO}/releases"
    exit 1
  fi

  RELEASE_TAG=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tag_name','unknown'))" 2>/dev/null)
  info "Downloading Changemaker Lite ${RELEASE_TAG}..."
  TARBALL_PATH="/tmp/changemaker-lite-install.tar.gz"
  curl -fSL "$TARBALL_URL" -o "$TARBALL_PATH"
  success "Downloaded $(du -h "$TARBALL_PATH" | cut -f1)"
fi

# =============================================================================
# Step 4: Extract
# =============================================================================

info "Extracting to ${INSTALL_DIR}..."

# Ensure the install directory exists and is empty
if [[ -d "$INSTALL_DIR" ]]; then
  # Remove any leftover files (may need sudo for root-owned Docker artifacts)
  rm -rf "${INSTALL_DIR:?}"/* "${INSTALL_DIR}"/.[!.]* 2>/dev/null || true
  if [[ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]]; then
    if command -v sudo &>/dev/null; then
      warn "Removing root-owned leftovers from previous install..."
      sudo rm -rf "${INSTALL_DIR:?}"/* "${INSTALL_DIR}"/.[!.]* 2>/dev/null || true
    fi
    if [[ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]]; then
      error "Cannot clean install directory ${INSTALL_DIR} — remove manually (may need sudo)"
      exit 1
    fi
  fi
else
  mkdir -p "$INSTALL_DIR"
fi

# Extract directly into INSTALL_DIR, stripping the tarball's root directory
tar xzf "$TARBALL_PATH" --strip-components=1 -C "$INSTALL_DIR"

if [[ ! -f "$INSTALL_DIR/docker-compose.yml" ]]; then
  error "Tarball extraction failed — docker-compose.yml not found"
  exit 1
fi
EXTRACT_COMPLETE=true

# Clean up downloaded tarball
if [[ -z "$LOCAL_TARBALL" ]] && [[ -f "$TARBALL_PATH" ]]; then
  rm -f "$TARBALL_PATH"
fi

success "Extracted to ${INSTALL_DIR}"

# =============================================================================
# Step 5: Run config wizard
# =============================================================================

echo ""
echo -e "${BOLD}Starting configuration wizard...${NC}"
echo ""
cd "$INSTALL_DIR"
# Redirect stdin from terminal — when piped (curl | bash), stdin is the pipe,
# so interactive prompts in config.sh would read garbage instead of user input.
if [[ -e /dev/tty ]]; then
  bash config.sh </dev/tty
else
  bash config.sh
fi
CONFIG_COMPLETE=true

# =============================================================================
# Step 6: Start services
# =============================================================================

echo ""
echo -e "${BOLD}Configuration complete!${NC}"
echo ""

START_SERVICES="y"
if [[ -t 0 ]]; then
  read -rp "Start all services now? [Y/n]: " START_SERVICES
  START_SERVICES=${START_SERVICES:-y}
elif [[ -e /dev/tty ]]; then
  read -rp "Start all services now? [Y/n]: " START_SERVICES </dev/tty
  START_SERVICES=${START_SERVICES:-y}
fi

if [[ "$START_SERVICES" =~ ^[Yy]$ ]]; then
  echo ""
  info "Pulling images from registry (this may take a few minutes on first run)..."
  echo ""
  cd "$INSTALL_DIR"

  # --ignore-pull-failures: optional services may not have images in the
  # registry yet — don't abort the whole install for those.
  if ! docker compose pull --ignore-pull-failures 2>&1; then
    echo ""
    error "Failed to pull images from the registry."
    echo ""
    echo "  Check that the registry is reachable:"
    echo "    curl -sf https://gitea.bnkops.com/api/v1/repos/admin/changemaker.lite/releases/latest"
    echo ""
    echo "  If pulling from a private registry, log in first:"
    echo "    docker login gitea.bnkops.com"
    echo ""
    echo "  Then retry:"
    echo "    cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
    exit 1
  fi
  success "All images pulled"

  echo ""
  info "Starting services..."
  if ! docker compose up -d 2>&1; then
    echo ""
    error "Failed to start services."
    echo "  Check logs: docker compose logs --tail 30"
    exit 1
  fi

  # --- Post-startup health verification ---
  echo ""
  info "Waiting for services to become healthy (up to ${HEALTH_TIMEOUT}s)..."
  info "  Database migrations and seeding run automatically on first boot."
  echo ""

  CORE_SERVICES=("v2-postgres" "redis" "api" "admin" "nginx")
  ELAPSED=0
  ALL_HEALTHY=false

  while [[ $ELAPSED -lt $HEALTH_TIMEOUT ]]; do
    ALL_HEALTHY=true

    for svc in "${CORE_SERVICES[@]}"; do
      # Detect crashed containers early
      state=$(docker compose ps "$svc" --format '{{.State}}' 2>/dev/null || echo "missing")
      if [[ "$state" == "exited" || "$state" == "dead" ]]; then
        echo ""
        error "Service '${svc}' exited unexpectedly. Last logs:"
        docker compose logs "$svc" --tail 20 2>/dev/null || true
        echo ""
        error "Fix the issue and retry: cd ${INSTALL_DIR} && docker compose up -d"
        exit 1
      fi

      # Check health status
      health=$(docker compose ps "$svc" --format '{{.Health}}' 2>/dev/null || echo "")
      if [[ "$health" != "healthy" ]]; then
        ALL_HEALTHY=false
      fi
    done

    if [[ "$ALL_HEALTHY" == "true" ]]; then
      break
    fi

    sleep "$HEALTH_INTERVAL"
    ELAPSED=$((ELAPSED + HEALTH_INTERVAL))
  done

  # Print status table
  echo ""
  echo -e "${BOLD}  Service Status:${NC}"
  for svc in "${CORE_SERVICES[@]}"; do
    health=$(docker compose ps "$svc" --format '{{.Health}}' 2>/dev/null || echo "unknown")
    state=$(docker compose ps "$svc" --format '{{.State}}' 2>/dev/null || echo "unknown")
    if [[ "$health" == "healthy" ]]; then
      echo -e "    ${GREEN}[healthy]${NC}  $svc"
    elif [[ "$state" == "running" ]]; then
      echo -e "    ${YELLOW}[starting]${NC} $svc"
    else
      echo -e "    ${RED}[${state}]${NC}    $svc"
    fi
  done
  echo ""

  if [[ "$ALL_HEALTHY" == "true" ]]; then
    success "All core services are healthy! (${ELAPSED}s)"
    echo ""
    echo "  Admin GUI:  http://localhost:3000"
    echo "  API:        http://localhost:4000"
    echo ""
    echo "  Check full stack:  docker compose ps"
    echo "  View API logs:     docker compose logs -f api --tail 20"
  else
    warn "Some services are still starting after ${HEALTH_TIMEOUT}s."
    echo ""
    echo "  This may be normal on first boot (migrations + seeding can be slow)."
    echo "  Monitor progress with:"
    echo "    docker compose logs -f api --tail 30"
    echo ""
    echo "  Check status with:"
    echo "    docker compose ps"
  fi
else
  echo ""
  info "Skipped. Start services manually when ready:"
  echo ""
  echo "    cd ${INSTALL_DIR} && docker compose up -d"
  echo ""
  echo "  Pre-built images will be pulled from the registry (~2 min first time)."
  echo "  Database migrations and seeding run automatically on startup."
fi

echo ""
echo -e "${BOLD}${GREEN}Installation complete!${NC}"
echo ""
echo -e "  \033[0;33mIMPORTANT: Change your admin password after first login!\033[0m"
echo ""
