#!/bin/bash
set -euo pipefail

# InfraPilot one-line installer.
#
#   curl -fsSL https://get.infrapilot.sh | bash                         # Community (free, keyless)
#   curl -fsSL https://get.infrapilot.sh | bash -s -- --license IP-...  # Enterprise (in place)
#
# Re-running updates in place. If a paid license is present (flag, env, or a prior
# install's .env) the script fetches private-registry pull creds from the license
# server, logs in, and runs the Enterprise image — reusing the SAME data volume, so
# CE→EE upgrades preserve your database and config. No key = Community, no signup.

INFRAPILOT_VERSION="${INFRAPILOT_VERSION:-latest}"
INSTALL_DIR="${INSTALL_DIR:-$HOME/infrapilot}"
LICENSE_KEY="${LICENSE_KEY:-}"
DO_UNINSTALL=0
PURGE=0
ASSUME_YES=0
BASE_URL="${INFRAPILOT_BASE_URL:-https://infrapilot.org}"
CE_COMPOSE_URL="$BASE_URL/docker-compose.install.yml"
EE_COMPOSE_URL="$BASE_URL/docker-compose.install.ee.yml"
EE_TOKEN_URL="$BASE_URL/api/license/ee-pull-token"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
USER="${USER:-$(whoami)}"

# ── Docker prerequisite (install it if missing, work out whether sudo is needed) ─────────
# Sets DOCKER to "docker" or "sudo docker" — every later docker/compose call in this script
# goes through $DOCKER so it works whether or not the current user has socket access yet.
SUDO=""
if [ "$(id -u)" -ne 0 ] && command -v sudo &> /dev/null; then
    SUDO="sudo"
fi
DOCKER="docker"

# confirm_docker_install asks before running a third-party script (get.docker.com) with
# root privileges. `curl | bash` consumes this script's own stdin, so a normal `read`
# would see EOF instantly — reading from /dev/tty instead reaches the real terminal, the
# same trick apt/npm-style installers use for prompts under a piped invocation.
confirm_docker_install() {
    if [ "$ASSUME_YES" -eq 1 ]; then
        return 0
    fi
    if [ ! -r /dev/tty ]; then
        echo -e "${RED}Error: Docker isn't installed, and this session has no terminal to confirm installing it.${NC}"
        echo "  Re-run with --yes to install non-interactively, or install Docker yourself: https://docs.docker.com/engine/install/"
        exit 1
    fi
    echo -e "${YELLOW}Docker isn't installed. InfraPilot needs it to run.${NC}"
    echo "  This will download and run get.docker.com${SUDO:+ with sudo}, installing Docker system-wide"
    [ -n "$SUDO" ] && echo "  and adding $USER to the docker group."
    read -r -p "  Continue? [y/N] " reply < /dev/tty
    case "$reply" in
        [yY]|[yY][eE][sS]) return 0 ;;
        *) echo "Aborted. Install Docker yourself: https://docs.docker.com/engine/install/"; exit 1 ;;
    esac
}

ensure_docker() {
    if ! command -v docker &> /dev/null; then
        if [ "$(id -u)" -ne 0 ] && [ -z "$SUDO" ]; then
            echo -e "${RED}Error: Docker isn't installed, and sudo isn't available to install it.${NC}"
            echo "  Install Docker yourself: https://docs.docker.com/engine/install/"
            exit 1
        fi
        confirm_docker_install
        echo -e "${YELLOW}→ Docker not found — installing via get.docker.com...${NC}"
        curl -fsSL https://get.docker.com | $SUDO sh
        if [ "$(id -u)" -ne 0 ]; then
            # Group membership only takes effect on the next login — use sudo for the rest
            # of *this* run rather than asking the user to log out mid-install.
            $SUDO usermod -aG docker "$USER" 2>/dev/null || true
            echo -e "${YELLOW}  Added $USER to the docker group — takes effect next login.${NC}"
            echo -e "${YELLOW}  Continuing this install with sudo for now.${NC}"
            DOCKER="sudo docker"
        fi
    elif ! docker info &> /dev/null 2>&1; then
        # docker CLI exists but the current user can't reach the daemon yet (e.g. installed
        # earlier in the same session, or this user was never added to the docker group).
        if [ -n "$SUDO" ]; then
            DOCKER="sudo docker"
        else
            echo -e "${RED}Error: cannot connect to the Docker daemon, and sudo isn't available.${NC}"
            echo "  Add your user to the docker group (sudo usermod -aG docker \$USER), log out/in, and re-run."
            exit 1
        fi
    fi

    # The daemon itself might not be running yet (get.docker.com enables+starts it via
    # systemd on most hosts, but that's a no-op on init systems without systemd) — a
    # missing-socket error here would otherwise surface later as a confusing "Docker
    # Compose v2 required" message that has nothing to do with the real problem.
    if ! $DOCKER info &> /dev/null 2>&1; then
        if [ -n "$SUDO" ] || [ "$(id -u)" -eq 0 ]; then
            $SUDO systemctl start docker 2> /dev/null || $SUDO service docker start 2> /dev/null || true
        fi
        if ! $DOCKER info &> /dev/null 2>&1; then
            echo -e "${RED}Error: Docker is installed but the daemon isn't running.${NC}"
            echo "  Try starting it yourself (e.g. sudo systemctl start docker) and re-run."
            exit 1
        fi
    fi
}

# ── Args ─────────────────────────────────────────────────────────────────────
while [ $# -gt 0 ]; do
    case "$1" in
        --license)   LICENSE_KEY="${2:-}"; shift 2 ;;
        --license=*) LICENSE_KEY="${1#*=}"; shift ;;
        --dir)       INSTALL_DIR="${2:-}"; shift 2 ;;
        --dir=*)     INSTALL_DIR="${1#*=}"; shift ;;
        --version)   INFRAPILOT_VERSION="${2:-}"; shift 2 ;;
        --version=*) INFRAPILOT_VERSION="${1#*=}"; shift ;;
        --uninstall) DO_UNINSTALL=1; shift ;;
        --purge)     PURGE=1; shift ;;
        -y|--yes)    ASSUME_YES=1; shift ;;
        -h|--help)
            echo "Usage: install.sh [--license KEY] [--dir PATH] [--version TAG] [--yes]"
            echo "       install.sh --uninstall [--purge]   # remove InfraPilot (--purge also deletes the data volume)"
            echo ""
            echo "  --yes   Skip the confirmation prompt before installing Docker (for scripted/non-interactive runs)."
            exit 0 ;;
        *) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;;
    esac
done

# ── Uninstall ────────────────────────────────────────────────────────────────
if [ "$DO_UNINSTALL" = "1" ]; then
    command -v docker &> /dev/null || { echo -e "${RED}docker not found${NC}"; exit 1; }
    if ! docker info &> /dev/null 2>&1 && [ -n "$SUDO" ]; then DOCKER="sudo docker"; fi
    if [ ! -d "$INSTALL_DIR" ]; then
        echo -e "${YELLOW}Nothing to uninstall at ${INSTALL_DIR}.${NC}"; exit 0
    fi
    if [ "$PURGE" = "1" ]; then
        echo -e "${BLUE}→ Uninstalling InfraPilot and deleting its data volume...${NC}"
        $DOCKER compose --project-directory "$INSTALL_DIR" down -v 2> /dev/null || true
    else
        echo -e "${BLUE}→ Uninstalling InfraPilot (keeping the data volume)...${NC}"
        $DOCKER compose --project-directory "$INSTALL_DIR" down 2> /dev/null || true
    fi
    rm -rf "$INSTALL_DIR"
    echo -e "${GREEN}✓ InfraPilot uninstalled.${NC}"
    if [ "$PURGE" = "1" ]; then
        echo "  Data volume removed."
    else
        echo "  Data volume kept — re-run with ${YELLOW}--uninstall --purge${NC} to delete your data too."
    fi
    exit 0
fi

echo ""
echo -e "${BLUE}  ___        __          ____  _ _       _   ${NC}"
echo -e "${BLUE} |_ _|_ __  / _|_ __ __ |  _ \\(_) | ___ | |_ ${NC}"
echo -e "${BLUE}  | || '_ \\| |_| '__/ _\`| |_) | | |/ _ \\| __|${NC}"
echo -e "${BLUE}  | || | | |  _| | | (_| |  __/| | | (_) | |_ ${NC}"
echo -e "${BLUE} |___|_| |_|_| |_|  \\__,_|_|  |_|_|\\___/ \\__|${NC}"
echo ""

# ── Prereqs ──────────────────────────────────────────────────────────────────
check_command() {
    if ! command -v "$1" &> /dev/null; then
        echo -e "${RED}Error: $1 is required but not installed.${NC}"
        echo "  Install $1 and try again."
        exit 1
    fi
}
check_command curl
ensure_docker
if ! $DOCKER compose version &> /dev/null; then
    echo -e "${RED}Error: Docker Compose v2 is required.${NC}"
    echo "  Install Docker Desktop or the Docker Compose plugin."
    exit 1
fi

# Extract a JSON string field without requiring jq.
json_get() {
    printf '%s' "$1" | grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 \
        | sed "s/.*:[[:space:]]*\"\\(.*\\)\"/\\1/"
}
gen_secret() {
    if command -v openssl &> /dev/null; then openssl rand -hex 32
    else (set +o pipefail; LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 64); fi
}
gen_uuid() {
    cat /proc/sys/kernel/random/uuid 2>/dev/null \
        || uuidgen 2>/dev/null \
        || openssl rand -hex 16 2>/dev/null \
        || echo "inst-$(date +%s)-$RANDOM"
}

# ── Carry over values from an existing install ───────────────────────────────
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"

existing_key=""; existing_jwt=""; existing_iid=""; existing_enc=""
if [ -f .env ]; then
    existing_key=$(grep '^LICENSE_KEY=' .env | cut -d= -f2- || true)
    existing_jwt=$(grep '^JWT_SECRET=' .env | cut -d= -f2- || true)
    existing_iid=$(grep '^INSTANCE_ID=' .env | cut -d= -f2- || true)
    existing_enc=$(grep '^ENCRYPTION_KEY=' .env | cut -d= -f2- || true)
fi
[ -z "$LICENSE_KEY" ] && [ -n "$existing_key" ] && LICENSE_KEY="$existing_key"
JWT_SECRET="${JWT_SECRET:-${existing_jwt:-$(gen_secret)}}"
INSTANCE_ID="${existing_iid:-$(gen_uuid)}"
# Encrypts secret-looking values at rest (deployment secrets, and stack/deploy env vars that
# look like passwords/tokens/keys) -- silently a no-op without this, so every install needs
# one. Same generator as JWT_SECRET (openssl rand -hex 32 -> a 32-byte AES-256 key).
ENCRYPTION_KEY="${ENCRYPTION_KEY:-${existing_enc:-$(gen_secret)}}"

# ── Resolve edition: paid license → Enterprise, else Community ───────────────
MODE="ce"
EE_IMAGE=""
if [ -n "$LICENSE_KEY" ]; then
    echo -e "${BLUE}→ Checking license...${NC}"
    hn="$(hostname 2>/dev/null || echo unknown)"
    resp=$(curl -fsS -m 20 -X POST "$EE_TOKEN_URL" \
        -H 'content-type: application/json' \
        -d "{\"key\":\"$LICENSE_KEY\",\"instance_id\":\"$INSTANCE_ID\",\"hostname\":\"$hn\",\"version\":\"$INFRAPILOT_VERSION\"}" \
        2>/dev/null || true)
    token=$(json_get "$resp" token)
    if [ -n "$token" ]; then
        reg=$(json_get "$resp" registry)
        user=$(json_get "$resp" username)
        img=$(json_get "$resp" image)
        ver=$(json_get "$resp" version)
        tier=$(json_get "$resp" tier)
        if echo "$token" | $DOCKER login "$reg" -u "$user" --password-stdin &> /dev/null; then
            MODE="ee"
            EE_IMAGE="$img"
            [ -n "$ver" ] && INFRAPILOT_VERSION="$ver"
            echo -e "${GREEN}  ✓ Enterprise Edition unlocked (${tier}).${NC}"
        else
            echo -e "${YELLOW}  ! Registry login failed — installing Community Edition.${NC}"
        fi
    else
        err=$(json_get "$resp" error)
        echo -e "${YELLOW}  ! ${err:-Could not reach the license server} — installing Community Edition.${NC}"
    fi
fi

if [ "$MODE" = "ce" ]; then
    echo -e "${GREEN}  Community Edition${NC}  (free · 1 server)"
fi
echo -e "  Version: ${INFRAPILOT_VERSION}"
echo ""

# ── Write config + compose, then start ───────────────────────────────────────
echo -e "${BLUE}→ Downloading configuration...${NC}"
if [ "$MODE" = "ee" ]; then
    curl -fsSL "$EE_COMPOSE_URL" -o docker-compose.yml
else
    curl -fsSL "$CE_COMPOSE_URL" -o docker-compose.yml
fi

{
    echo "JWT_SECRET=${JWT_SECRET}"
    echo "ENCRYPTION_KEY=${ENCRYPTION_KEY}"
    echo "LICENSE_KEY=${LICENSE_KEY}"
    echo "INSTANCE_ID=${INSTANCE_ID}"
    echo "INFRAPILOT_VERSION=${INFRAPILOT_VERSION}"
    [ "$MODE" = "ee" ] && echo "EE_IMAGE=${EE_IMAGE}"
} > .env
chmod 600 .env

echo -e "${BLUE}→ Pulling InfraPilot image...${NC}"
$DOCKER compose pull

echo -e "${BLUE}→ Starting InfraPilot...${NC}"
$DOCKER compose up -d

echo -e "${BLUE}→ Waiting for InfraPilot to be ready...${NC}"
for _ in $(seq 1 30); do
    if curl -sf http://localhost/api/health > /dev/null 2>&1; then break; fi
    sleep 2
done

# The setup token only exists pre-setup (it's deleted once the first admin account is
# created), so a re-run against an already-set-up install won't print anything here —
# that's expected, not an error. Read it straight out of the container instead of
# pointing people at `docker logs`, which doesn't capture the backend's own output (it's
# redirected to a file inside the container by supervisord).
SETUP_TOKEN=$($DOCKER compose --project-directory "$INSTALL_DIR" exec -T infrapilot cat /data/setup_token 2> /dev/null || true)

# Best-effort only — most installs are on a remote box accessed by IP, and "Dashboard:
# http://localhost" alone is misleading there (that's the *box's* localhost, not yours).
# Silently skipped if there's no outbound internet access to ask (e.g. an air-gapped host).
PUBLIC_IP=$(curl -s -m 2 https://ifconfig.me 2> /dev/null || true)

echo ""
echo -e "${GREEN}================================================${NC}"
if [ "$MODE" = "ee" ]; then
    echo -e "${GREEN}  InfraPilot Enterprise is running!${NC}"
else
    echo -e "${GREEN}  InfraPilot is running!${NC}"
fi
echo -e "${GREEN}================================================${NC}"
echo ""
echo "  Dashboard:  http://localhost"
if [ -n "$PUBLIC_IP" ]; then
    echo "              http://$PUBLIC_IP   (if you're accessing this box remotely)"
fi
echo "  Data dir:   $INSTALL_DIR"
echo ""
if [ -n "$SETUP_TOKEN" ]; then
    echo -e "  ${BLUE}Setup token (needed to create your admin account):${NC}"
    echo "    $SETUP_TOKEN"
    echo ""
fi
if [ "$MODE" = "ce" ]; then
    echo -e "  ${BLUE}Unlock more servers, scanning & SSO:${NC}"
    echo "    1. Buy a plan at $BASE_URL/pricing"
    echo "    2. Re-run:  curl -fsSL https://get.infrapilot.sh | bash -s -- --license YOUR_KEY"
    echo "    (upgrades in place — your data is preserved)"
    echo ""
fi
echo "  To update:  curl -fsSL https://get.infrapilot.sh | bash"
echo "  To stop:      docker compose --project-directory $INSTALL_DIR down"
echo "  To uninstall: curl -fsSL https://get.infrapilot.sh | bash -s -- --uninstall        (add --purge to delete data)"
echo ""
