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

MODE="${1:---activate}"
case "$MODE" in
  --preflight|--activate) ;;
  *) printf '[Learnitive www vhost] ERROR: expected --preflight or --activate, got %s\n' "$MODE" >&2; exit 2 ;;
esac

SITE_ROOT='/var/www/learnitive.com'
APP_ROOT="$SITE_ROOT/public_html"
APACHE_ROOT='/etc/apache2'
AVAILABLE="$APACHE_ROOT/sites-available"
ENABLED="$APACHE_ROOT/sites-enabled"
SOURCE_APACHE="$APP_ROOT/deploy/apache"
VERIFY_SCRIPT="$APP_ROOT/scripts/verify-www-local-vhost.sh"
MARKER='/etc/learnitive/.www-vhosts'

MANAGED_SITES=(
  'www.learnitive.com-http.conf'
  'www.learnitive.com-https.conf'
  'learnitive.com-redirect-https.conf'
  'edu.learnitive.com-http.conf'
  'edu.learnitive.com-https.conf'
)
TARGET_HOSTS=(
  'www.learnitive.com'
  'learnitive.com'
  'edu.learnitive.com'
)

SUCCESS=0
CHANGED=0
BACKUP_DIR=''
PROTECTED_APACHE_BEFORE=''
PROTECTED_APACHE_AFTER=''
LEGACY_ENABLED_BASENAMES=()
WWW_CERT_FILE=''
WWW_KEY_FILE=''
APEX_CERT_FILE=''
APEX_KEY_FILE=''
EDU_CERT_FILE=''
EDU_KEY_FILE=''

log(){ printf '[Learnitive www vhost] %s\n' "$*"; }
warn(){ printf '[Learnitive www vhost] WARNING: %s\n' "$*" >&2; }
die(){ printf '[Learnitive www vhost] ERROR: %s\n' "$*" >&2; exit 1; }
require(){ command -v "$1" >/dev/null 2>&1 || die "Required command is missing: $1"; }

is_managed_basename(){
  local needle="$1" item
  for item in "${MANAGED_SITES[@]}"; do [[ "$needle" == "$item" ]] && return 0; done
  return 1
}

is_target_host(){
  local needle="${1,,}" host
  needle="${needle%.}"
  for host in "${TARGET_HOSTS[@]}"; do [[ "$needle" == "$host" ]] && return 0; done
  return 1
}

source_for_site(){
  case "$1" in
    edu.learnitive.com-http.conf) printf '%s\n' "$SOURCE_APACHE/edu.learnitive.com-cutover-redirect-http.conf" ;;
    edu.learnitive.com-https.conf) printf '%s\n' "$SOURCE_APACHE/edu.learnitive.com-cutover-redirect-https.conf" ;;
    *) printf '%s\n' "$SOURCE_APACHE/$1" ;;
  esac
}

snapshot_protected_apache(){
  local out="$1" root file target
  : > "$out"
  for root in "$AVAILABLE" "$ENABLED"; do
    [[ -d "$root" ]] || continue
    while IFS= read -r -d '' file; do
      if grep -Eqi 'interactiveshell\.com|interactive-shell|Interactive Shell' "$file" 2>/dev/null; then
        if [[ -L "$file" ]]; then
          target="$(readlink "$file")"
          printf 'L\t%s\t%s\n' "$file" "$target" >> "$out"
        elif [[ -f "$file" ]]; then
          printf 'F\t%s\t%s\n' "$file" "$(sha256sum "$file" | awk '{print $1}')" >> "$out"
        fi
      fi
    done < <(find "$root" -maxdepth 1 \( -type f -o -type l \) -print0 | sort -z)
  done
  sort -o "$out" "$out"
}

extract_vhost_names(){
  # Emit every ServerName/ServerAlias token from an Apache site file, ignoring comments.
  awk '
    /^[[:space:]]*#/ { next }
    /^[[:space:]]*(ServerName|ServerAlias)[[:space:]]+/ {
      for (i=2; i<=NF; i++) {
        if ($i ~ /^#/) break;
        print tolower($i);
      }
    }
  ' "$1" 2>/dev/null | sed -E 's/[.]$//' | sort -u
}

file_claims_target_host(){
  local file="$1" name
  while IFS= read -r name; do
    is_target_host "$name" && return 0
  done < <(extract_vhost_names "$file")
  return 1
}

file_is_learnitive_target_only(){
  local file="$1" name count=0
  while IFS= read -r name; do
    [[ -n "$name" ]] || continue
    count=$((count + 1))
    is_target_host "$name" || return 1
  done < <(extract_vhost_names "$file")
  (( count > 0 ))
}

scan_legacy_enabled_vhosts(){
  LEGACY_ENABLED_BASENAMES=()
  local file base names
  while IFS= read -r -d '' file; do
    base="$(basename -- "$file")"
    is_managed_basename "$base" && continue
    file_claims_target_host "$file" || continue
    if ! file_is_learnitive_target_only "$file"; then
      names="$(extract_vhost_names "$file" | paste -sd ',' -)"
      die "Enabled Apache file $file mixes a production/staging Learnitive hostname with another hostname ($names). Refusing automatic cutover so unrelated vhosts are not changed."
    fi
    LEGACY_ENABLED_BASENAMES+=("$base")
  done < <(find "$ENABLED" -maxdepth 1 \( -type f -o -type l \) -print0 | sort -z)
}

cert_matches_key(){
  local cert="$1" key="$2" cert_pub key_pub
  cert_pub="$(openssl x509 -in "$cert" -pubkey -noout 2>/dev/null | openssl pkey -pubin -outform DER 2>/dev/null | sha256sum | awk '{print $1}')"
  key_pub="$(openssl pkey -in "$key" -pubout -outform DER 2>/dev/null | sha256sum | awk '{print $1}')"
  [[ -n "$cert_pub" && "$cert_pub" == "$key_pub" ]]
}

cert_covers_host(){
  local cert="$1" host="$2"
  openssl x509 -in "$cert" -noout -checkend 3600 >/dev/null 2>&1 || return 1
  openssl x509 -in "$cert" -noout -checkhost "$host" 2>/dev/null | grep -qi 'does match certificate'
}

find_enabled_vhost_certificate_for_host(){
  local host="$1" file name cert key
  while IFS= read -r -d '' file; do
    local owns=0
    while IFS= read -r name; do
      [[ "$name" == "$host" ]] && owns=1 && break
    done < <(extract_vhost_names "$file")
    (( owns == 1 )) || continue
    cert="$(awk '/^[[:space:]]*SSLCertificateFile[[:space:]]+/ {print $2; exit}' "$file" 2>/dev/null || true)"
    key="$(awk '/^[[:space:]]*SSLCertificateKeyFile[[:space:]]+/ {print $2; exit}' "$file" 2>/dev/null || true)"
    [[ "$cert" == /* && "$key" == /* && -r "$cert" && -r "$key" ]] || continue
    cert_covers_host "$cert" "$host" || continue
    cert_matches_key "$cert" "$key" || continue
    printf '%s\t%s\n' "$cert" "$key"
    return 0
  done < <(find "$ENABLED" -maxdepth 1 \( -type f -o -type l \) -print0 | sort -z)
  return 1
}

find_certificate_for_host(){
  local host="$1" cert_var="$2" key_var="$3" generic_ok="${4:-0}"
  local cert="${!cert_var:-}" key="${!key_var:-}" candidate
  if [[ -z "$cert" && -z "$key" && "$generic_ok" == '1' && -n "${LEARNITIVE_TLS_CERT_FILE:-}" ]]; then
    cert="$LEARNITIVE_TLS_CERT_FILE"
    key="${LEARNITIVE_TLS_KEY_FILE:-}"
  fi
  if [[ -n "$cert" || -n "$key" ]]; then
    [[ -n "$cert" && -n "$key" ]] || die "Set both $cert_var and $key_var for $host."
    [[ -r "$cert" && -r "$key" ]] || die "Configured TLS certificate/key for $host is not readable."
    cert_covers_host "$cert" "$host" || die "Configured TLS certificate does not validly cover $host or expires within one hour: $cert"
    cert_matches_key "$cert" "$key" || die "Configured TLS certificate and key do not match for $host."
    printf '%s\t%s\n' "$cert" "$key"
    return 0
  fi

  local existing
  if existing="$(find_enabled_vhost_certificate_for_host "$host")"; then
    printf '%s\n' "$existing"
    return 0
  fi

  if [[ -d /etc/letsencrypt/live ]]; then
    while IFS= read -r candidate; do
      key="$(dirname -- "$candidate")/privkey.pem"
      [[ -r "$candidate" && -r "$key" ]] || continue
      cert_covers_host "$candidate" "$host" || continue
      cert_matches_key "$candidate" "$key" || continue
      printf '%s\t%s\n' "$candidate" "$key"
      return 0
    done < <(find /etc/letsencrypt/live -mindepth 2 -maxdepth 2 -name fullchain.pem \( -type f -o -type l \) 2>/dev/null | sort)
  fi
  return 1
}

resolve_certificates(){
  local pair
  pair="$(find_certificate_for_host 'www.learnitive.com' LEARNITIVE_WWW_TLS_CERT_FILE LEARNITIVE_WWW_TLS_KEY_FILE 1)" \
    || die 'No existing readable TLS certificate/key covering www.learnitive.com was found. Provision the certificate first, or set LEARNITIVE_WWW_TLS_CERT_FILE and LEARNITIVE_WWW_TLS_KEY_FILE.'
  IFS=$'\t' read -r WWW_CERT_FILE WWW_KEY_FILE <<<"$pair"

  pair="$(find_certificate_for_host 'learnitive.com' LEARNITIVE_APEX_TLS_CERT_FILE LEARNITIVE_APEX_TLS_KEY_FILE 1)" \
    || die 'No existing readable TLS certificate/key covering learnitive.com was found. Provision the certificate first, or set LEARNITIVE_APEX_TLS_CERT_FILE and LEARNITIVE_APEX_TLS_KEY_FILE.'
  IFS=$'\t' read -r APEX_CERT_FILE APEX_KEY_FILE <<<"$pair"

  pair="$(find_certificate_for_host 'edu.learnitive.com' LEARNITIVE_EDU_TLS_CERT_FILE LEARNITIVE_EDU_TLS_KEY_FILE 0)" \
    || die 'No existing readable TLS certificate/key covering edu.learnitive.com was found. The one-shot production cutover redirects edu over HTTPS, so its current certificate must remain available.'
  IFS=$'\t' read -r EDU_CERT_FILE EDU_KEY_FILE <<<"$pair"
}

assert_prerequisites(){
  [[ ${EUID:-$(id -u)} -eq 0 ]] || die 'Run as root.'
  [[ "$(cd -- "$(dirname -- "$0")/../.." && pwd)" == "$APP_ROOT" ]] || die "This helper must run from $APP_ROOT."
  local cmd site src modules module
  for cmd in apache2ctl openssl curl install cp rm ln readlink grep awk sed sha256sum find sort mktemp mkdir cmp sleep basename dirname paste date; do require "$cmd"; done
  [[ -d "$AVAILABLE" && -d "$ENABLED" ]] || die 'Apache sites-available/sites-enabled directories are missing.'
  [[ -f "$VERIFY_SCRIPT" ]] || die 'Local www vhost verification script is missing.'
  for site in "${MANAGED_SITES[@]}"; do
    src="$(source_for_site "$site")"
    [[ -f "$src" ]] || die "Release vhost template is missing: $src"
  done

  # Do not mutate the module set on a web server shared with other products.
  modules="$(apache2ctl -M 2>/dev/null || true)"
  for module in rewrite proxy proxy_http proxy_wstunnel headers ssl; do
    grep -Eq "[[:space:]]${module}_module[[:space:]]" <<<"$modules" || die "Required Apache module is not enabled: $module (refusing to mutate the shared Apache module set)."
  done

  scan_legacy_enabled_vhosts
  resolve_certificates
}

assert_activation_frontend_ready(){
  local dist="$APP_ROOT/dist" index="$APP_ROOT/dist/index.html"
  [[ -d "$dist" ]] || die "Refusing Apache activation before the production frontend exists: $dist is missing. Run the application installer/build first."
  [[ -s "$index" ]] || die "Refusing Apache activation before the production frontend is ready: $index is missing or empty. Run the application installer/build first."
  [[ -r "$index" ]] || die "Refusing Apache activation because the production frontend entrypoint is not readable: $index"
}

render_site(){
  local site="$1" out="$2" src cert='' key=''
  src="$(source_for_site "$site")"
  cp -- "$src" "$out"
  case "$site" in
    www.learnitive.com-https.conf) cert="$WWW_CERT_FILE"; key="$WWW_KEY_FILE" ;;
    learnitive.com-redirect-https.conf) cert="$APEX_CERT_FILE"; key="$APEX_KEY_FILE" ;;
    edu.learnitive.com-https.conf) cert="$EDU_CERT_FILE"; key="$EDU_KEY_FILE" ;;
  esac
  if [[ -n "$cert" ]]; then
    sed -i -E \
      -e "s#^[[:space:]]*SSLCertificateFile[[:space:]].*#    SSLCertificateFile $cert#" \
      -e "s#^[[:space:]]*SSLCertificateKeyFile[[:space:]].*#    SSLCertificateKeyFile $key#" \
      "$out"
  fi
}

rollback(){
  local status=$?
  trap - EXIT
  if (( SUCCESS == 0 && CHANGED == 1 )); then
    warn "Vhost activation failed; restoring the previous Learnitive Apache site files from $BACKUP_DIR."
    local site base
    for site in "${MANAGED_SITES[@]}"; do
      rm -f -- "$AVAILABLE/$site" "$ENABLED/$site"
      [[ -e "$BACKUP_DIR/available/$site" || -L "$BACKUP_DIR/available/$site" ]] && cp -a -- "$BACKUP_DIR/available/$site" "$AVAILABLE/$site"
      [[ -e "$BACKUP_DIR/enabled/$site" || -L "$BACKUP_DIR/enabled/$site" ]] && cp -a -- "$BACKUP_DIR/enabled/$site" "$ENABLED/$site"
    done
    for base in "${LEGACY_ENABLED_BASENAMES[@]}"; do
      rm -f -- "$ENABLED/$base"
      [[ -e "$BACKUP_DIR/legacy-enabled/$base" || -L "$BACKUP_DIR/legacy-enabled/$base" ]] && cp -a -- "$BACKUP_DIR/legacy-enabled/$base" "$ENABLED/$base"
    done
    if apache2ctl configtest >/dev/null 2>&1; then
      apache2ctl graceful >/dev/null 2>&1 || warn 'Previous Apache configuration was restored but graceful reload failed; inspect Apache manually.'
    else
      warn 'Previous Learnitive vhost files were restored, but Apache configtest is not clean; inspect Apache manually.'
    fi
  fi
  exit "$status"
}
trap rollback EXIT

assert_prerequisites
if [[ "$MODE" == '--preflight' ]]; then
  log "Preflight passed: Apache modules are ready; TLS is available for www, apex and edu; ${#LEGACY_ENABLED_BASENAMES[@]} legacy Learnitive-only enabled vhost file(s) can be migrated safely."
  exit 0
fi

# --preflight is deliberately usable before the build.  --activate is not:
# the managed www vhost depends on dist both as its DocumentRoot and as the
# directory rewrite context that proxies public SPA routes to the SEO renderer.
# Failing here leaves the currently enabled Apache sites untouched.
assert_activation_frontend_ready

BACKUP_DIR="/var/backups/learnitive/apache-vhosts/$(date -u +%Y%m%dT%H%M%SZ)-$$"
mkdir -p "$BACKUP_DIR/available" "$BACKUP_DIR/enabled" "$BACKUP_DIR/legacy-enabled" "$BACKUP_DIR/rendered"
PROTECTED_APACHE_BEFORE="$BACKUP_DIR/protected-apache.before"
PROTECTED_APACHE_AFTER="$BACKUP_DIR/protected-apache.after"
snapshot_protected_apache "$PROTECTED_APACHE_BEFORE"

for site in "${MANAGED_SITES[@]}"; do
  [[ -e "$AVAILABLE/$site" || -L "$AVAILABLE/$site" ]] && cp -a -- "$AVAILABLE/$site" "$BACKUP_DIR/available/$site"
  [[ -e "$ENABLED/$site" || -L "$ENABLED/$site" ]] && cp -a -- "$ENABLED/$site" "$BACKUP_DIR/enabled/$site"
done
for base in "${LEGACY_ENABLED_BASENAMES[@]}"; do
  cp -a -- "$ENABLED/$base" "$BACKUP_DIR/legacy-enabled/$base"
  rm -f -- "$ENABLED/$base"
done

for site in "${MANAGED_SITES[@]}"; do
  render_site "$site" "$BACKUP_DIR/rendered/$site"
  install -o root -g root -m 0644 "$BACKUP_DIR/rendered/$site" "$AVAILABLE/$site"
  rm -f -- "$ENABLED/$site"
  ln -s "../sites-available/$site" "$ENABLED/$site"
done
CHANGED=1

# Ensure no Interactive Shell Apache vhost/config file was touched before Apache parses
# and gracefully reloads the new Learnitive-only production/redirect sites.
snapshot_protected_apache "$PROTECTED_APACHE_AFTER"
cmp -s "$PROTECTED_APACHE_BEFORE" "$PROTECTED_APACHE_AFTER" || die 'Protected Interactive Shell Apache vhost/config content changed while installing Learnitive sites.'

apache2ctl configtest >/dev/null || die 'Apache configtest failed with the new Learnitive vhosts.'
log 'Apache configtest passed; activating only Learnitive production + redirect vhosts with a graceful reload.'
apache2ctl graceful
sleep 1
bash "$VERIFY_SCRIPT" || die 'Local www/apex/edu HTTPS verification failed after vhost activation.'

snapshot_protected_apache "$PROTECTED_APACHE_AFTER"
cmp -s "$PROTECTED_APACHE_BEFORE" "$PROTECTED_APACHE_AFTER" || die 'Protected Interactive Shell Apache vhost/config content changed after Learnitive activation.'

install -d -o root -g root -m 0750 /etc/learnitive
cat > "$MARKER" <<MARKER_EOF
domain=www.learnitive.com
apex=learnitive.com
edu_redirect=www.learnitive.com
backup=$BACKUP_DIR
www_cert=$WWW_CERT_FILE
apex_cert=$APEX_CERT_FILE
edu_cert=$EDU_CERT_FILE
MARKER_EOF
chmod 0640 "$MARKER"

SUCCESS=1
CHANGED=0
log "Production www + apex vhosts and the edu -> www redirect are active and verified. Previous Learnitive vhost state is retained at $BACKUP_DIR. Interactive Shell Apache config files were unchanged."
