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

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
ENV_EXAMPLE="$ROOT_DIR/.env.example"
DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.selfhost.yml"
if [[ ! -f "$DEFAULT_COMPOSE_FILE" ]]; then
  DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
fi
COMPOSE_FILE="${FEDEO_COMPOSE_FILE:-$DEFAULT_COMPOSE_FILE}"

MODE=""
START_STACK="ask"
FORCE="false"
UNINSTALL="false"
PURGE="false"
TTY_INPUT="${FEDEO_TTY_INPUT:-/dev/tty}"
TTY_FD=""

if { exec 3<"$TTY_INPUT"; } 2>/dev/null; then
  TTY_FD="3"
fi

read_interactive() {
  if [[ -n "$TTY_FD" ]]; then
    read -u "$TTY_FD" "$@"
  else
    read "$@"
  fi
}

docker_engine() {
  if [[ "${FEDEO_USE_SUDO_DOCKER:-false}" == "true" ]]; then
    sudo docker "$@"
  else
    docker "$@"
  fi
}

compose() {
  if [[ "${FEDEO_USE_SUDO_DOCKER:-false}" == "true" ]]; then
    sudo docker compose "$@"
  else
    docker compose "$@"
  fi
}

compose_stack() {
  compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
}

compose_start_command() {
  if [[ "${FEDEO_USE_SUDO_DOCKER:-false}" == "true" ]]; then
    echo "sudo docker compose --env-file $ENV_FILE -f $COMPOSE_FILE up -d --remove-orphans"
  else
    echo "docker compose --env-file $ENV_FILE -f $COMPOSE_FILE up -d --remove-orphans"
  fi
}

remove_selfhost_networks() {
  local network

  for network in fedeo_web fedeo_internal; do
    if docker_engine network inspect "$network" >/dev/null 2>&1; then
      if docker_engine network rm "$network" >/dev/null 2>&1; then
        echo "Docker-Netzwerk entfernt: $network"
      else
        echo "Docker-Netzwerk konnte nicht entfernt werden: $network"
        echo "Prüfe laufende Container mit: docker network inspect $network"
      fi
    fi
  done
}

usage() {
  cat <<'USAGE'
FEDEO Selfhost Setup

Nutzung:
  bash scripts/selfhost-setup.sh
  bash scripts/selfhost-setup.sh --simple
  bash scripts/selfhost-setup.sh --advanced
  bash scripts/selfhost-setup.sh --simple --start
  bash scripts/selfhost-setup.sh --uninstall
  bash scripts/selfhost-setup.sh --uninstall --purge

Optionen:
  --simple     Kurzer Assistent mit lokalen Diensten und sicheren Defaults
  --advanced   Fragt zusätzliche Integrationen wie SMTP, OpenAI und DATEV-nahes Umfeld ab
  --start      Startet den Stack nach dem Schreiben der Konfiguration
  --no-start   Schreibt nur Konfiguration und Verzeichnisse
  --force      Überschreibt eine vorhandene .env ohne Rückfrage
  --uninstall  Stoppt und entfernt den Docker-Compose-Stack
  --purge      Entfernt beim Uninstall zusätzlich .env und lokale Datenverzeichnisse
USAGE
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --simple)
      MODE="simple"
      ;;
    --advanced)
      MODE="advanced"
      ;;
    --start)
      START_STACK="yes"
      ;;
    --no-start)
      START_STACK="no"
      ;;
    --force)
      FORCE="true"
      ;;
    --uninstall)
      UNINSTALL="true"
      ;;
    --purge)
      PURGE="true"
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "Unbekannte Option: $1" >&2
      usage
      exit 1
      ;;
  esac
  shift
done

need_file() {
  if [[ ! -f "$1" ]]; then
    echo "Fehlt: $1" >&2
    exit 1
  fi
}

random_secret() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 48 | tr -d '\n'
  else
    LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64
  fi
}

random_hex_secret() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -hex 32 | tr -d '\n'
  else
    LC_ALL=C tr -dc 'a-f0-9' </dev/urandom | head -c 64
  fi
}

env_quote() {
  local value="$1"
  value="${value//\'/\'\\\'\'}"
  printf "'%s'" "$value"
}

prompt() {
  local label="$1"
  local default_value="${2:-}"
  local value

  if [[ -n "$default_value" ]]; then
    read_interactive -r -p "$label [$default_value]: " value
    echo "${value:-$default_value}"
  else
    read_interactive -r -p "$label: " value
    echo "$value"
  fi
}

prompt_required() {
  local label="$1"
  local default_value="${2:-}"
  local value

  while true; do
    value="$(prompt "$label" "$default_value")"
    if [[ -n "$value" ]]; then
      echo "$value"
      return
    fi
    echo "Bitte einen Wert eintragen." >&2
  done
}

prompt_secret() {
  local label="$1"
  local default_value="${2:-}"
  local value

  if [[ -n "$default_value" ]]; then
    read_interactive -r -s -p "$label [vorbelegt, Enter übernimmt]: " value
    echo >&2
    echo "${value:-$default_value}"
  else
    read_interactive -r -s -p "$label: " value
    echo >&2
    echo "$value"
  fi
}

yes_no() {
  local label="$1"
  local default_value="${2:-n}"
  local answer

  while true; do
    read_interactive -r -p "$label [$default_value]: " answer
    answer="${answer:-$default_value}"
    case "$answer" in
      y|Y|j|J|yes|Yes|ja|Ja)
        return 0
        ;;
      n|N|no|No|nein|Nein)
        return 1
        ;;
      *)
        echo "Bitte ja oder nein eingeben."
        ;;
    esac
  done
}

choose_mode() {
  if [[ -n "$MODE" ]]; then
    return
  fi

  echo
  echo "Setup-Modus"
  echo "  1) einfach  - Domain, Admin, lokale Datenbank, MinIO, Matrix"
  echo "  2) advanced - zusätzlich SMTP, externe Schlüssel und optionale Dienste"
  echo

  local choice
  while true; do
    read_interactive -r -p "Modus wählen [1]: " choice
    case "${choice:-1}" in
      1|einfach|simple)
        MODE="simple"
        return
        ;;
      2|advanced|erweitert)
        MODE="advanced"
        return
        ;;
      *)
        echo "Bitte 1 oder 2 wählen."
        ;;
    esac
  done
}

print_structure_guide() {
  cat <<EOF

FEDEO Selfhost Setup

Dieses Script führt dich durch die lokale Betriebsstruktur:

  $ROOT_DIR/
    $(basename "$COMPOSE_FILE")           Docker Stack für FEDEO, Traefik, PostgreSQL, MinIO, Matrix und Monitoring
    .env                         Zielkonfiguration, wird von diesem Script geschrieben
    postgres/                    persistente FEDEO-Datenbank
    minio/                       lokaler S3-kompatibler Dateispeicher
    matrix/postgres/             persistente Synapse-Datenbank
    matrix/synapse/              generierte Synapse-Konfiguration und Matrix-Daten
    traefik/letsencrypt/         Let's-Encrypt-Zertifikate
    traefik/logs/                Traefik-Logs

Öffentliche Endpunkte auf einer Domain:

  https://DOMAIN/                FEDEO Frontend
  https://DOMAIN/backend         FEDEO API
  https://DOMAIN/_matrix         Matrix Homeserver
  https://DOMAIN/.well-known     Matrix Discovery
  https://DOMAIN/livekit/sfu     LiveKit
  https://DOMAIN/livekit/jwt     LiveKit JWT-Service
  https://DOMAIN/element         Element Web

EOF
}

write_env() {
  local domain="$1"
  local contact_email="$2"
  local db_password="$3"
  local minio_password="$4"
  local cookie_secret="$5"
  local jwt_secret="$6"
  local encryption_key="$7"
  local m2m_key="$8"
  local admin_email="$9"
  local admin_password="${10}"
  local admin_first_name="${11}"
  local admin_last_name="${12}"
  local tenant_name="${13}"
  local tenant_short="${14}"
  local matrix_db_password="${15}"
  local matrix_turn_secret="${16}"
  local matrix_registration_secret="${17}"
  local livekit_secret="${18}"
  local mailer_host="${19}"
  local mailer_port="${20}"
  local mailer_ssl="${21}"
  local mailer_user="${22}"
  local mailer_pass="${23}"
  local mailer_from="${24}"
  local web_push_public="${25}"
  local web_push_private="${26}"
  local pdf_license="${27}"
  local openai_key="${28}"
  local stirling_key="${29}"
  local gocardless_secret_id="${30}"
  local gocardless_secret_key="${31}"
  local dokubox_host="${32}"
  local dokubox_port="${33}"
  local dokubox_secure="${34}"
  local dokubox_user="${35}"
  local dokubox_password="${36}"

  cat >"$ENV_FILE" <<EOF
# FEDEO Selfhosting
DOMAIN=$(env_quote "$domain")
CONTACT_EMAIL=$(env_quote "$contact_email")

DB_NAME=$(env_quote "fedeo")
DB_USER=$(env_quote "fedeo")
DB_PASSWORD=$(env_quote "$db_password")
DATABASE_URL=$(env_quote "postgres://fedeo:$db_password@db:5432/fedeo")

MINIO_ROOT_USER=$(env_quote "fedeo-minio")
MINIO_ROOT_PASSWORD=$(env_quote "$minio_password")
MINIO_BUCKET=$(env_quote "fedeo")

HOST=$(env_quote "0.0.0.0")
PORT=$(env_quote "3100")
FEDEO_RUN_MIGRATIONS=$(env_quote "true")
COOKIE_SECRET=$(env_quote "$cookie_secret")
JWT_SECRET=$(env_quote "$jwt_secret")
ENCRYPTION_KEY=$(env_quote "$encryption_key")

MAILER_SMTP_HOST=$(env_quote "$mailer_host")
MAILER_SMTP_PORT=$(env_quote "$mailer_port")
MAILER_SMTP_SSL=$(env_quote "$mailer_ssl")
MAILER_SMTP_USER=$(env_quote "$mailer_user")
MAILER_SMTP_PASS=$(env_quote "$mailer_pass")
MAILER_FROM=$(env_quote "$mailer_from")

WEB_PUSH_PUBLIC_KEY=$(env_quote "$web_push_public")
WEB_PUSH_PRIVATE_KEY=$(env_quote "$web_push_private")
WEB_PUSH_SUBJECT=$(env_quote "mailto:$contact_email")

S3_ENDPOINT=$(env_quote "http://minio:9000")
S3_REGION=$(env_quote "eu-central-1")
S3_ACCESS_KEY=$(env_quote "fedeo-minio")
S3_SECRET_KEY=$(env_quote "$minio_password")
S3_BUCKET=$(env_quote "fedeo")

FEDEO_FILE_BACKEND=$(env_quote "s3")
SEAFILE_BASE_URL=$(env_quote "https://files.example.com")
SEAFILE_INTERNAL_URL=$(env_quote "https://files.example.com")
SEAFILE_ADMIN_EMAIL=$(env_quote "$admin_email")
SEAFILE_ADMIN_PASSWORD=$(env_quote "change-this-seafile-admin-password")

M2M_API_KEY=$(env_quote "$m2m_key")
API_BASE_URL=$(env_quote "https://$domain/backend")
GOCARDLESS_BASE_URL=$(env_quote "https://api.gocardless.com")
GOCARDLESS_SECRET_ID=$(env_quote "$gocardless_secret_id")
GOCARDLESS_SECRET_KEY=$(env_quote "$gocardless_secret_key")

DOKUBOX_IMAP_HOST=$(env_quote "$dokubox_host")
DOKUBOX_IMAP_PORT=$(env_quote "$dokubox_port")
DOKUBOX_IMAP_SECURE=$(env_quote "$dokubox_secure")
DOKUBOX_IMAP_USER=$(env_quote "$dokubox_user")
DOKUBOX_IMAP_PASSWORD=$(env_quote "$dokubox_password")

OPENAI_API_KEY=$(env_quote "$openai_key")
STIRLING_API_KEY=$(env_quote "$stirling_key")
NUXT_PUBLIC_PDF_LICENSE=$(env_quote "$pdf_license")
NODE_EXPORTER_URL=$(env_quote "http://node-exporter:9100")

FEDEO_BOOTSTRAP_ADMIN_EMAIL=$(env_quote "$admin_email")
FEDEO_BOOTSTRAP_ADMIN_PASSWORD=$(env_quote "$admin_password")
FEDEO_BOOTSTRAP_ADMIN_FIRST_NAME=$(env_quote "$admin_first_name")
FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=$(env_quote "$admin_last_name")
FEDEO_BOOTSTRAP_TENANT_NAME=$(env_quote "$tenant_name")
FEDEO_BOOTSTRAP_TENANT_SHORT=$(env_quote "$tenant_short")
FEDEO_BOOTSTRAP_MATRIX=$(env_quote "true")

MATRIX_SERVER_NAME=$(env_quote "$domain")
MATRIX_POSTGRES_DB=$(env_quote "synapse")
MATRIX_POSTGRES_USER=$(env_quote "synapse")
MATRIX_POSTGRES_PASSWORD=$(env_quote "$matrix_db_password")
MATRIX_TURN_SHARED_SECRET=$(env_quote "$matrix_turn_secret")
LIVEKIT_KEY=$(env_quote "fedeo-livekit")
LIVEKIT_SECRET=$(env_quote "$livekit_secret")
MATRIX_HOMESERVER_URL=$(env_quote "http://matrix-synapse:8008")
MATRIX_RTC_HOST=$(env_quote "$domain")
MATRIX_RTC_JWT_URL=$(env_quote "https://$domain/livekit/jwt")
MATRIX_LIVEKIT_URL=$(env_quote "wss://$domain/livekit/sfu")
MATRIX_REGISTRATION_SHARED_SECRET=$(env_quote "$matrix_registration_secret")
MATRIX_SERVICE_USER_LOCALPART=$(env_quote "fedeo_service")
NUXT_PUBLIC_MATRIX_ELEMENT_URL=$(env_quote "https://$domain/element")
EOF
}

prepare_directories() {
  mkdir -p \
    "$ROOT_DIR/traefik/letsencrypt" \
    "$ROOT_DIR/traefik/logs" \
    "$ROOT_DIR/postgres" \
    "$ROOT_DIR/minio" \
    "$ROOT_DIR/matrix/postgres" \
    "$ROOT_DIR/matrix/synapse"

  touch "$ROOT_DIR/traefik/letsencrypt/acme.json"
  chmod 600 "$ROOT_DIR/traefik/letsencrypt/acme.json"
}

uninstall_stack() {
  need_file "$COMPOSE_FILE"

  if [[ ! -f "$ENV_FILE" ]]; then
    echo "Keine .env gefunden. Docker Compose wird mit Standardwerten ausgeführt."
  fi

  echo
  echo "FEDEO Selfhost Uninstall"
  echo "Compose-Datei: $COMPOSE_FILE"
  echo

  if [[ "$FORCE" != "true" ]]; then
    if ! yes_no "Docker-Compose-Stack stoppen und Container/Netzwerke entfernen?" "n"; then
      echo "Abgebrochen."
      exit 0
    fi
  fi

  if [[ -f "$ENV_FILE" ]]; then
    compose_stack down --remove-orphans
  else
    compose -f "$COMPOSE_FILE" down --remove-orphans
  fi

  if [[ "$PURGE" != "true" ]]; then
    echo
    echo "Uninstall abgeschlossen. Lokale Daten und .env wurden behalten."
    echo "Für vollständiges Entfernen erneut mit --uninstall --purge ausführen."
    return
  fi

  echo
  echo "Achtung: --purge entfernt lokale Datenbanken, Dateien, Matrix-Daten, Zertifikate und .env."
  echo "Betroffene Pfade:"
  echo "  $ENV_FILE"
  echo "  $ROOT_DIR/postgres"
  echo "  $ROOT_DIR/minio"
  echo "  $ROOT_DIR/matrix/postgres"
  echo "  $ROOT_DIR/matrix/synapse"
  echo "  $ROOT_DIR/traefik/letsencrypt"
  echo "  $ROOT_DIR/traefik/logs"

  if [[ "$FORCE" != "true" ]]; then
    if ! yes_no "Diese lokalen Daten endgültig löschen?" "n"; then
      echo "Daten wurden behalten."
      return
    fi
  fi

  rm -rf \
    "$ENV_FILE" \
    "$ROOT_DIR/postgres" \
    "$ROOT_DIR/minio" \
    "$ROOT_DIR/matrix/postgres" \
    "$ROOT_DIR/matrix/synapse" \
    "$ROOT_DIR/traefik/letsencrypt" \
    "$ROOT_DIR/traefik/logs"

  remove_selfhost_networks

  echo
  echo "Uninstall inklusive lokaler Daten abgeschlossen."
}

main() {
  if [[ "$UNINSTALL" == "true" ]]; then
    uninstall_stack
    return
  fi

  if [[ "$PURGE" == "true" ]]; then
    echo "--purge kann nur zusammen mit --uninstall verwendet werden." >&2
    exit 1
  fi

  need_file "$COMPOSE_FILE"
  need_file "$ENV_EXAMPLE"

  print_structure_guide
  choose_mode

  if [[ -f "$ENV_FILE" && "$FORCE" != "true" ]]; then
    if yes_no ".env existiert bereits. Backup erstellen und überschreiben?" "n"; then
      cp "$ENV_FILE" "$ENV_FILE.$(date +%Y%m%d-%H%M%S).bak"
    else
      echo "Abgebrochen, .env bleibt unverändert."
      exit 0
    fi
  fi

  echo
  echo "Basis"
  local domain contact_email admin_email admin_password admin_first_name admin_last_name tenant_name tenant_short
  domain="$(prompt_required "Domain für FEDEO" "app.example.com")"
  contact_email="$(prompt_required "Kontakt-E-Mail für Let's Encrypt" "admin@example.com")"
  admin_email="$(prompt_required "Erster Admin-Login" "$contact_email")"
  admin_password="$(prompt_secret "Erstes Admin-Passwort" "$(random_secret)")"
  admin_first_name="$(prompt_required "Admin-Vorname" "Admin")"
  admin_last_name="$(prompt_required "Admin-Nachname" "Benutzer")"
  tenant_name="$(prompt_required "Name des ersten Mandanten" "Mein Unternehmen")"
  tenant_short="$(prompt_required "Kurzname des ersten Mandanten" "MEIN")"

  echo
  echo "Secrets werden automatisch erzeugt."
  local db_password minio_password cookie_secret jwt_secret encryption_key m2m_key
  local matrix_db_password matrix_turn_secret matrix_registration_secret livekit_secret
  db_password="$(random_hex_secret)"
  minio_password="$(random_secret)"
  cookie_secret="$(random_secret)"
  jwt_secret="$(random_secret)"
  encryption_key="$(random_hex_secret)"
  m2m_key="$(random_secret)"
  matrix_db_password="$(random_secret)"
  matrix_turn_secret="$(random_secret)"
  matrix_registration_secret="$(random_secret)"
  livekit_secret="$(random_secret)"

  local mailer_host="smtp.example.com"
  local mailer_port="587"
  local mailer_ssl="false"
  local mailer_user="mailer@example.com"
  local mailer_pass="change-this-mail-password"
  local mailer_from="FEDEO <no-reply@example.com>"
  local web_push_public="replace-this-web-push-public-key"
  local web_push_private="replace-this-web-push-private-key"
  local pdf_license="replace-with-your-pdf-license"
  local openai_key="replace-this"
  local stirling_key="replace-this"
  local gocardless_secret_id="replace-this"
  local gocardless_secret_key="replace-this"
  local dokubox_host="imap.example.com"
  local dokubox_port="993"
  local dokubox_secure="true"
  local dokubox_user="dokubox@example.com"
  local dokubox_password="change-this-imap-password"

  if [[ "$MODE" == "advanced" ]]; then
    echo
    echo "Advanced: SMTP"
    mailer_host="$(prompt "SMTP Host" "$mailer_host")"
    mailer_port="$(prompt "SMTP Port" "$mailer_port")"
    mailer_ssl="$(prompt "SMTP SSL true/false" "$mailer_ssl")"
    mailer_user="$(prompt "SMTP Benutzer" "$mailer_user")"
    mailer_pass="$(prompt_secret "SMTP Passwort" "$mailer_pass")"
    mailer_from="$(prompt "Absender" "FEDEO <no-reply@$domain>")"

    echo
    echo "Advanced: optionale Schlüssel"
    pdf_license="$(prompt "PDF-Lizenz" "$pdf_license")"
    openai_key="$(prompt_secret "OpenAI API Key" "$openai_key")"
    stirling_key="$(prompt_secret "Stirling API Key" "$stirling_key")"
    web_push_public="$(prompt "Web Push Public Key" "$web_push_public")"
    web_push_private="$(prompt_secret "Web Push Private Key" "$web_push_private")"

    echo
    echo "Advanced: Banking und Dokubox"
    gocardless_secret_id="$(prompt "GoCardless Secret ID" "$gocardless_secret_id")"
    gocardless_secret_key="$(prompt_secret "GoCardless Secret Key" "$gocardless_secret_key")"
    dokubox_host="$(prompt "Dokubox IMAP Host" "$dokubox_host")"
    dokubox_port="$(prompt "Dokubox IMAP Port" "$dokubox_port")"
    dokubox_secure="$(prompt "Dokubox IMAP Secure true/false" "$dokubox_secure")"
    dokubox_user="$(prompt "Dokubox IMAP Benutzer" "$dokubox_user")"
    dokubox_password="$(prompt_secret "Dokubox IMAP Passwort" "$dokubox_password")"
  fi

  write_env \
    "$domain" "$contact_email" "$db_password" "$minio_password" \
    "$cookie_secret" "$jwt_secret" "$encryption_key" "$m2m_key" \
    "$admin_email" "$admin_password" "$admin_first_name" "$admin_last_name" \
    "$tenant_name" "$tenant_short" "$matrix_db_password" "$matrix_turn_secret" \
    "$matrix_registration_secret" "$livekit_secret" "$mailer_host" "$mailer_port" \
    "$mailer_ssl" "$mailer_user" "$mailer_pass" "$mailer_from" "$web_push_public" \
    "$web_push_private" "$pdf_license" "$openai_key" "$stirling_key" \
    "$gocardless_secret_id" "$gocardless_secret_key" "$dokubox_host" \
    "$dokubox_port" "$dokubox_secure" "$dokubox_user" "$dokubox_password"

  prepare_directories

  echo
  echo "Konfiguration geschrieben:"
  echo "  $ENV_FILE"
  echo
  echo "Vor dem Start prüfen:"
  echo "  1. DNS zeigt auf diesen Server: $domain"
  echo "  2. Ports 80, 443, 3478/tcp, 3478/udp, 5349/tcp, 49160-49200/udp sind offen"
  echo "  3. Platzhalter für optionale Dienste in .env bei Bedarf ersetzen"

  if [[ "$START_STACK" == "ask" ]]; then
    if yes_no "Stack jetzt mit Docker Compose starten?" "n"; then
      START_STACK="yes"
    else
      START_STACK="no"
    fi
  fi

  if [[ "$START_STACK" == "yes" ]]; then
    compose_stack up -d --remove-orphans
  else
    echo
    echo "Start später mit:"
    echo "  $(compose_start_command)"
  fi
}

main
