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

KILLIAN_VERSION="1.1.0"
DEFAULT_RELEASE_BASE_URL="${KILLIAN_RELEASE_BASE_URL:-http://192.168.1.99/releases/${KILLIAN_VERSION}}"
INSTALL_ROOT="/opt"
INSTALL_DIR="${INSTALL_ROOT}/Killian"
ARCHIVE="Killian-${KILLIAN_VERSION}.tar.gz"
CHECKSUM="${ARCHIVE}.sha256"

say() {
    printf '%s\n' "$*"
}

fail() {
    printf 'ERROR: %s\n' "$*" >&2
    exit 1
}

prompt_default() {
    local prompt="$1"
    local default="$2"
    local value
    read -r -p "${prompt} [${default}]: " value
    printf '%s' "${value:-$default}"
}

if [[ "${EUID}" -ne 0 ]]; then
    fail "Run this installer with sudo: sudo ./install-killian.sh"
fi

if [[ -e "${INSTALL_DIR}" ]]; then
    fail "${INSTALL_DIR} already exists. This installer will not overwrite an existing Killian installation."
fi

say "============================================================"
say " Killian ${KILLIAN_VERSION} Installer"
say "============================================================"
say ""

if [[ -r /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    if [[ "${ID:-}" != "ubuntu" ]]; then
        say "WARNING: This installer was validated on Ubuntu Server 24.04."
    elif [[ "${VERSION_ID:-}" != "24.04" ]]; then
        say "WARNING: Ubuntu ${VERSION_ID:-unknown} detected; 24.04 is the validated release."
    fi
fi

say "Installing host prerequisites..."
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ca-certificates \
    curl \
    tar \
    gzip \
    coreutils \
    python3

if ! command -v docker >/dev/null 2>&1 || ! docker compose version >/dev/null 2>&1; then
    say "Installing Docker Engine and Docker Compose from Docker's official repository..."

    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
        -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc

    . /etc/os-release
    cat > /etc/apt/sources.list.d/docker.sources <<DOCKER_REPO
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: ${UBUNTU_CODENAME:-$VERSION_CODENAME}
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
DOCKER_REPO

    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
        docker-ce \
        docker-ce-cli \
        containerd.io \
        docker-buildx-plugin \
        docker-compose-plugin
else
    say "Docker Engine and Docker Compose are already available."
fi

if ! docker buildx version >/dev/null 2>&1; then
    if apt-cache show docker-buildx-plugin >/dev/null 2>&1; then
        DEBIAN_FRONTEND=noninteractive apt-get install -y docker-buildx-plugin
    elif apt-cache show docker-buildx >/dev/null 2>&1; then
        DEBIAN_FRONTEND=noninteractive apt-get install -y docker-buildx
    fi
fi

systemctl enable --now docker

docker --version
docker compose version

say ""
release_base_url="$(prompt_default "Killian release URL" "${DEFAULT_RELEASE_BASE_URL}")"
release_base_url="${release_base_url%/}"

say ""
say "Deployment profile:"
say "  1) Local LAN / HTTP"
say "  2) FQDN / HTTPS behind a reverse proxy"
read -r -p "Select profile [1]: " profile
profile="${profile:-1}"

case "${profile}" in
    1)
        detected_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
        detected_ip="${detected_ip:-127.0.0.1}"
        public_host="$(prompt_default "Killian server IP or hostname" "${detected_ip}")"
        allowed_hosts="${public_host}"
        trusted_origins="http://${public_host}:8000"
        cookie_secure="0"
        ssl_redirect="0"
        hsts_seconds="0"
        trusted_proxy_ips=""
        setup_url="http://${public_host}:8000/setup/"
        ;;
    2)
        read -r -p "Killian FQDN (example: killian.example.com): " public_host
        [[ -n "${public_host}" ]] || fail "An FQDN is required for the HTTPS profile."
        allowed_hosts="${public_host}"
        trusted_origins="https://${public_host}"
        cookie_secure="1"
        ssl_redirect="1"
        hsts_seconds="31536000"
        read -r -p "Trusted reverse proxy IP/CIDR (leave blank if not needed): " trusted_proxy_ips
        setup_url="https://${public_host}/setup/"
        ;;
    *)
        fail "Invalid deployment profile."
        ;;
esac

timezone="$(prompt_default "Application timezone" "America/New_York")"

work_dir="$(mktemp -d)"
cleanup() {
    rm -rf "${work_dir}"
}
trap cleanup EXIT

say ""
say "Downloading Killian ${KILLIAN_VERSION}..."
cd "${work_dir}"
curl -fL "${release_base_url}/${ARCHIVE}" -o "${ARCHIVE}"
curl -fL "${release_base_url}/${CHECKSUM}" -o "${CHECKSUM}"
sha256sum -c "${CHECKSUM}"

say "Extracting Killian to ${INSTALL_DIR}..."
mkdir -p "${INSTALL_ROOT}"
tar -xzf "${ARCHIVE}" -C "${INSTALL_ROOT}"
mv "${INSTALL_ROOT}/Killian-${KILLIAN_VERSION}" "${INSTALL_DIR}"
chown -R root:root "${INSTALL_DIR}"

say "Creating protected environment configuration..."
cd "${INSTALL_DIR}"
export KILLIAN_SETUP_ALLOWED_HOSTS="${allowed_hosts}"
export KILLIAN_SETUP_TRUSTED_ORIGINS="${trusted_origins}"
export KILLIAN_SETUP_COOKIE_SECURE="${cookie_secure}"
export KILLIAN_SETUP_SSL_REDIRECT="${ssl_redirect}"
export KILLIAN_SETUP_HSTS_SECONDS="${hsts_seconds}"
export KILLIAN_SETUP_TIMEZONE="${timezone}"
export KILLIAN_SETUP_TRUSTED_PROXY_IPS="${trusted_proxy_ips}"

python3 - <<'PY'
from pathlib import Path
import base64
import os
import secrets

trusted_proxy = os.environ.get('KILLIAN_SETUP_TRUSTED_PROXY_IPS', '').strip()
proxy_line = f"TRUSTED_PROXY_IPS={trusted_proxy}\n" if trusted_proxy else ''

env = f"""POSTGRES_DB=killian
POSTGRES_USER=killian
POSTGRES_PASSWORD={secrets.token_urlsafe(36)}

DJANGO_SECRET_KEY={secrets.token_urlsafe(64)}
KILLIAN_MASTER_KEY={base64.urlsafe_b64encode(os.urandom(32)).decode()}

DJANGO_DEBUG=0

ALLOWED_HOSTS={os.environ['KILLIAN_SETUP_ALLOWED_HOSTS']}
CSRF_TRUSTED_ORIGINS={os.environ['KILLIAN_SETUP_TRUSTED_ORIGINS']}
COOKIE_SECURE={os.environ['KILLIAN_SETUP_COOKIE_SECURE']}
SECURE_SSL_REDIRECT={os.environ['KILLIAN_SETUP_SSL_REDIRECT']}
HSTS_SECONDS={os.environ['KILLIAN_SETUP_HSTS_SECONDS']}
{proxy_line}
TZ={os.environ['KILLIAN_SETUP_TIMEZONE']}
MFA_ISSUER=Killian

SESSION_IDLE_TIMEOUT_SECONDS=7200
INVITE_LINK_TIMEOUT=86400
IMPORT_STAGING_RETENTION_SECONDS=86400
IMPORT_RECOVERY_RETENTION_SECONDS=2592000
AUTH_RATE_LIMIT_WINDOW_SECONDS=900
LOGIN_RATE_LIMIT_ATTEMPTS=30
PASSWORD_RESET_RATE_LIMIT_ATTEMPTS=10
IP_CONTROL_RULE_CACHE_SECONDS=5
IP_CONTROL_AUDIT_INTERVAL_SECONDS=60
"""

path = Path('.env')
path.write_text(env)
os.chmod(path, 0o600)
PY

unset KILLIAN_SETUP_ALLOWED_HOSTS \
    KILLIAN_SETUP_TRUSTED_ORIGINS \
    KILLIAN_SETUP_COOKIE_SECURE \
    KILLIAN_SETUP_SSL_REDIRECT \
    KILLIAN_SETUP_HSTS_SECONDS \
    KILLIAN_SETUP_TIMEZONE \
    KILLIAN_SETUP_TRUSTED_PROXY_IPS

docker compose config --quiet

say "Building and starting Killian..."
docker compose up -d --build

say "Waiting for the application container..."
ready=0
for _ in $(seq 1 60); do
    if docker compose exec -T app python manage.py check >/dev/null 2>&1; then
        ready=1
        break
    fi
    sleep 2
done

if [[ "${ready}" -ne 1 ]]; then
    docker compose ps
    docker compose logs --tail=80 app || true
    fail "Killian did not become ready. Review the container output above."
fi

say ""
say "============================================================"
say " Killian ${KILLIAN_VERSION} installation complete"
say "============================================================"
say ""
say "Open this URL in a browser to create the initial administrator:"
say ""
say "  ${setup_url}"
say ""
say "The web setup will guide you through administrator creation and the"
say "initial two-factor authentication policy. No CLI superuser command is required."
say ""
say "Keep ${INSTALL_DIR}/.env protected and back up KILLIAN_MASTER_KEY securely."
