#!/bin/bash
# FileWave Let's Encrypt installer (Debian 12/13)
# Supports:
#   - HTTP-01 validation (standalone, requires inbound TCP/80)
#   - DNS-01 validation via Cloudflare plugin (no TCP/80 requirement)
#
# UX-focused Debian script with modular challenge handlers.

set -euo pipefail

LOG_FILE="/var/log/filewave-letsencrypt.log"
RENEW_HOOK="/etc/letsencrypt/renewal-hooks/deploy/filewave-server-cert.sh"
CRON_FILE="/etc/cron.daily/letsencrypt-filewave"
CF_SECRETS_DIR="/etc/letsencrypt/secrets"
CF_CREDENTIALS_FILE="${CF_SECRETS_DIR}/cloudflare.ini"

METHOD=""
HOSTNAME=""
EMAIL=""
CLOUDFLARE_TOKEN=""
OS_VERSION="unknown"
STEP_INDEX=0
TOTAL_STEPS=10

# ---------- Output formatting (no dependencies) ----------
if [ -t 1 ] && [ "${NO_COLOR:-}" = "" ]; then
  C_RESET='\033[0m'
  C_BOLD='\033[1m'
  C_DIM='\033[2m'
  C_BLUE='\033[34m'
  C_GREEN='\033[32m'
  C_YELLOW='\033[33m'
  C_RED='\033[31m'
else
  C_RESET=''
  C_BOLD=''
  C_DIM=''
  C_BLUE=''
  C_GREEN=''
  C_YELLOW=''
  C_RED=''
fi

banner() {
  echo
  echo -e "${C_BOLD}==============================================================${C_RESET}"
  echo -e "${C_BOLD}  FileWave Let's Encrypt Installer (Debian 12/13)${C_RESET}"
  echo -e "${C_BOLD}==============================================================${C_RESET}"
  echo
}

log() {
  # Always log plain text (no ANSI codes) to file.
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

info() { echo -e "${C_BLUE}[INFO]${C_RESET} $*"; }
ok()   { echo -e "${C_GREEN}[OK]${C_RESET}   $*"; }
warn() { echo -e "${C_YELLOW}[WARN]${C_RESET} $*"; }
err()  { echo -e "${C_RED}[ERR]${C_RESET}  $*"; }

step() {
  STEP_INDEX=$((STEP_INDEX + 1))
  echo
  echo -e "${C_BOLD}[${STEP_INDEX}/${TOTAL_STEPS}]${C_RESET} $*"
}

die() {
  err "$*"
  log "ERROR: $*"
  exit 1
}

usage() {
  cat <<USAGE
FileWave Let's Encrypt installer for Debian 12/13.

Usage:
  sudo ./filewave-letsencrypt-debian.sh --install
  sudo ./filewave-letsencrypt-debian.sh --uninstall

Install flow prompts for:
  - FQDN
  - email
  - challenge method:
      1) HTTP-01 (standalone)
      2) DNS-01 (Cloudflare)

Notes:
  - HTTP-01 requires inbound TCP/80 reachability.
  - DNS-01 (Cloudflare) requires an API token with DNS edit rights.
USAGE
}

# ---------- Validation + helpers ----------
require_root() {
  if [ "$(id -u)" -ne 0 ]; then
    echo "root rights required - please rerun as"
    echo "sudo ${BASH_SOURCE[0]} --install"
    exit 1
  fi
}

require_debian() {
  [ -f /etc/os-release ] || die "/etc/os-release not found"
  grep -q 'Debian' /etc/os-release || die "This script is designed to run on Debian"

  OS_VERSION=$(grep '^VERSION_ID=' /etc/os-release | cut -d'=' -f2 | tr -d '"')
  case "$OS_VERSION" in
    12|13) ok "Debian $OS_VERSION detected" ;;
    *) warn "Validated for Debian 12/13. Detected VERSION_ID=$OS_VERSION" ;;
  esac
}

ensure_filewave_server() {
  [ -x /usr/local/bin/fwcontrol ] || die "FileWave control binary not found: /usr/local/bin/fwcontrol"
  [ -d /usr/local/filewave/certs ] || die "FileWave cert directory not found: /usr/local/filewave/certs"

  if [ ! -x /usr/local/filewave/python/bin/python ]; then
    warn "FileWave python not found at /usr/local/filewave/python/bin/python (update_dep_profile_certs may not run)"
  fi
}

validate_hostname() {
  local hostname="$1"
  [[ "$hostname" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$ ]] || return 1
  [ ${#hostname} -le 253 ] || return 1
  return 0
}

validate_email() {
  local email="$1"
  [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]] || return 1
  return 0
}

prompt_yes_no() {
  local prompt="$1"
  local answer
  while true; do
    read -r -p "$prompt [yes/no]: " answer
    case "${answer,,}" in
      yes|y) return 0 ;;
      no|n) return 1 ;;
      *) warn "Please answer yes or no." ;;
    esac
  done
}

# ---------- Package/tool setup ----------
ensure_dnsutils() {
  if ! command -v nslookup >/dev/null 2>&1; then
    info "Installing dnsutils..."
    apt-get update -y
    apt-get install -y dnsutils
    ok "dnsutils installed"
  fi
}

check_public_dns_resolution() {
  local hostname="$1"
  ensure_dnsutils

  if nslookup "$hostname" 8.8.8.8 >/dev/null 2>&1; then
    ok "Public DNS resolution check passed for $hostname via 8.8.8.8"
    return 0
  fi

  warn "$hostname did not resolve via 8.8.8.8; trying system resolver"
  nslookup "$hostname" >/dev/null 2>&1 || die "$hostname did not resolve via 8.8.8.8 or system resolver"
  ok "DNS resolution check passed for $hostname via system resolver"
}

update_system_packages() {
  info "Updating package metadata..."
  apt-get update -y
  info "Upgrading packages (noninteractive)..."
  DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -o Dpkg::Options::="--force-confold"
  ok "System package update/upgrade complete"
}

ensure_certbot_base() {
  if command -v certbot >/dev/null 2>&1; then
    ok "certbot already present"
    return 0
  fi

  info "Installing snapd and certbot..."
  apt-get install -y snapd
  snap install core
  snap refresh core || true

  apt-get remove -y certbot python3-certbot-apache 2>/dev/null || true
  snap install --classic certbot
  ln -sf /snap/bin/certbot /usr/bin/certbot

  command -v certbot >/dev/null 2>&1 || die "Certbot installation failed"
  ok "certbot installed"
}

ensure_cloudflare_plugin() {
  info "Ensuring certbot-dns-cloudflare plugin is installed (snap stack)..."

  if ! snap install certbot-dns-cloudflare 2>/dev/null; then
    snap refresh certbot-dns-cloudflare >/dev/null 2>&1 || true
  fi

  certbot plugins 2>/dev/null | grep -qi 'dns-cloudflare' || {
    die "Cloudflare DNS plugin not detected by certbot (snap). Keep certbot/plugin on the same packaging stack."
  }
  ok "Cloudflare DNS plugin available"
}

# ---------- Cert + FileWave tasks ----------
setup_cloudflare_credentials() {
  mkdir -p "$CF_SECRETS_DIR"
  chmod 700 "$CF_SECRETS_DIR"

  cat > "$CF_CREDENTIALS_FILE" <<CRED
# Cloudflare token used by certbot dns-cloudflare plugin
dns_cloudflare_api_token = $CLOUDFLARE_TOKEN
CRED

  chmod 600 "$CF_CREDENTIALS_FILE"

  [ -f "$CF_CREDENTIALS_FILE" ] || die "Failed to create $CF_CREDENTIALS_FILE"
  [ "$(stat -c '%a' "$CF_CREDENTIALS_FILE")" = "600" ] || die "$CF_CREDENTIALS_FILE permission should be 600"
  ok "Cloudflare credentials created ($CF_CREDENTIALS_FILE, mode 600)"
}

verify_cloudflare_credentials_exists() {
  [ -f "$CF_CREDENTIALS_FILE" ] || die "Cloudflare credentials file missing: $CF_CREDENTIALS_FILE"
  [ -r "$CF_CREDENTIALS_FILE" ] || die "Cloudflare credentials file unreadable: $CF_CREDENTIALS_FILE"
}

backup_existing_certs() {
  local backup_date
  backup_date=$(date +%Y-%m-%d-%H-%M)
  local backup_dir="/usr/local/filewave/certs/backup-${backup_date}"

  mkdir -p "$backup_dir"
  if cp -p /usr/local/filewave/certs/server.* "$backup_dir" 2>/dev/null; then
    ok "Existing certificates backed up to $backup_dir"
  else
    warn "Existing certificates not found for backup (continuing)"
  fi
}

request_certificate_http() {
  info "Requesting certificate via HTTP-01 (standalone)..."
  certbot -n --agree-tos --standalone certonly -d "$HOSTNAME" -m "$EMAIL" || {
    echo
    err "Certificate request failed (HTTP-01)."
    echo "Likely causes:"
    echo "  - TCP/80 not reachable from internet"
    echo "  - DNS record not pointing to this server"
    echo
    echo "Manual retry:"
    echo "sudo certbot -n --agree-tos --standalone certonly -d \"$HOSTNAME\" -m \"$EMAIL\""
    die "Stopping after HTTP-01 failure"
  }
  ok "Certificate issued (HTTP-01)"
}

request_certificate_dns_cloudflare() {
  verify_cloudflare_credentials_exists

  info "Requesting certificate via DNS-01 (Cloudflare)..."
  certbot -n --agree-tos --dns-cloudflare --dns-cloudflare-credentials "$CF_CREDENTIALS_FILE" certonly -d "$HOSTNAME" -m "$EMAIL" || {
    echo
    err "Certificate request failed (DNS-01 Cloudflare)."
    echo "Likely causes:"
    echo "  - invalid Cloudflare token"
    echo "  - token lacks DNS edit permission on this zone"
    echo "  - FQDN not in a zone accessible by that token"
    echo
    echo "Manual retry:"
    echo "sudo certbot -n --agree-tos --dns-cloudflare --dns-cloudflare-credentials $CF_CREDENTIALS_FILE certonly -d \"$HOSTNAME\" -m \"$EMAIL\""
    die "Stopping after DNS-01 failure"
  }
  ok "Certificate issued (DNS-01 Cloudflare)"
}

set_mdm_cert_trusted() {
  info "Updating iOS preferences in PostgreSQL (mdm_cert_trusted=true)..."
  if /usr/local/filewave/postgresql/bin/psql -d mdm -U django -c \
    "INSERT INTO ios_preferences (key, value) VALUES ('mdm_cert_trusted', TRUE) ON CONFLICT (key) DO NOTHING; UPDATE ios_preferences SET value='true' WHERE key='mdm_cert_trusted';" \
    2>/dev/null; then
    ok "iOS preferences updated"
  else
    warn "Failed to update iOS preferences (continuing)"
  fi
}

create_renew_hook() {
  mkdir -p "$(dirname "$RENEW_HOOK")"
  rm -f "$RENEW_HOOK"

  cat > "$RENEW_HOOK" <<HOOK
#!/bin/bash
set -e
HOSTNAME="$HOSTNAME"

if [ -e /usr/local/filewave/certs/server.crt ]; then
  CERT_OWNER=\$(stat -c '%U' /usr/local/filewave/certs/server.crt 2>/dev/null || echo root)
  CERT_GROUP=\$(stat -c '%G' /usr/local/filewave/certs/server.crt 2>/dev/null || echo root)
else
  CERT_OWNER=\$(stat -c '%U' /usr/local/filewave/certs 2>/dev/null || echo root)
  CERT_GROUP=\$(stat -c '%G' /usr/local/filewave/certs 2>/dev/null || echo root)
fi

cp /etc/letsencrypt/live/"\$HOSTNAME"/fullchain.pem /usr/local/filewave/certs/server.crt
cp /etc/letsencrypt/live/"\$HOSTNAME"/privkey.pem /usr/local/filewave/certs/server.key
chown "\$CERT_OWNER":"\$CERT_GROUP" /usr/local/filewave/certs/server.*

/usr/local/bin/fwcontrol server restart
(yes 2>/dev/null) | /usr/local/filewave/python/bin/python /usr/local/filewave/django/manage.pyc update_dep_profile_certs 2>/dev/null || true
HOOK

  chmod +x "$RENEW_HOOK"
  bash -n "$RENEW_HOOK" || die "Renewal hook syntax check failed"
  ok "Renewal hook created: $RENEW_HOOK"
}

inject_certificate_now() {
  info "Injecting certificate into FileWave now..."
  "$RENEW_HOOK" || die "Initial certificate injection failed"
  ok "Certificate injected into FileWave"
}

create_renew_cron() {
  cat > "$CRON_FILE" <<'CRON'
#!/bin/bash
sleep $((RANDOM % 7200))
/usr/bin/certbot renew --quiet
CRON

  chmod +x "$CRON_FILE"
  ok "Renewal cron created: $CRON_FILE"
}

uninstall_files() {
  step "Removing FileWave Let's Encrypt integration files"
  log "Starting uninstall flow"

  rm -f "$RENEW_HOOK"
  rm -f "$CRON_FILE"

  if [ -f "$CF_CREDENTIALS_FILE" ]; then
    if command -v shred >/dev/null 2>&1; then
      shred -vfz -n 3 "$CF_CREDENTIALS_FILE" || rm -f "$CF_CREDENTIALS_FILE"
    else
      rm -f "$CF_CREDENTIALS_FILE"
    fi
    ok "Removed Cloudflare credential file"
  else
    info "No Cloudflare credential file found (nothing to remove)"
  fi

  ok "Uninstall complete"
  echo
  echo "certbot package was intentionally left installed."
}

# ---------- Prompting ----------
prompt_hostname() {
  while true; do
    read -r -p "Enter fully qualified domain name (FQDN): " HOSTNAME
    if validate_hostname "$HOSTNAME"; then
      return 0
    fi
    warn "Invalid hostname format. Example: fw.example.com"
  done
}

prompt_email() {
  while true; do
    read -r -p "Enter email address for Let's Encrypt notifications: " EMAIL
    if validate_email "$EMAIL"; then
      return 0
    fi
    warn "Invalid email format. Example: admin@example.com"
  done
}

prompt_method() {
  while true; do
    echo
    echo "Select Let's Encrypt validation method:"
    echo "  1) HTTP-01 (standalone certbot)"
    echo "     - Requires inbound TCP 80 from internet"
    echo "     - Simpler setup, no DNS API token"
    echo
    echo "  2) DNS-01 (Cloudflare)"
    echo "     - Does NOT require inbound TCP 80"
    echo "     - Requires Cloudflare API token with Zone.DNS edit rights"
    echo
    read -r -p "Choice [1/2]: " method_choice

    case "$method_choice" in
      1) METHOD="http"; return 0 ;;
      2) METHOD="dns-cloudflare"; return 0 ;;
      *) warn "Invalid choice. Enter 1 or 2." ;;
    esac
  done
}

prompt_cloudflare_token_if_needed() {
  if [ "$METHOD" = "dns-cloudflare" ]; then
    while true; do
      read -r -s -p "Enter Cloudflare API token: " CLOUDFLARE_TOKEN
      echo
      if [ -n "$CLOUDFLARE_TOKEN" ]; then
        return 0
      fi
      warn "Cloudflare token cannot be empty."
    done
  fi
}

collect_install_inputs() {
  step "Collecting install inputs"

  prompt_hostname
  prompt_email
  prompt_method
  prompt_cloudflare_token_if_needed

  echo
  echo -e "${C_BOLD}Review before continuing:${C_RESET}"
  echo "  Hostname: $HOSTNAME"
  echo "  Email:    $EMAIL"
  if [ "$METHOD" = "http" ]; then
    echo "  Method:   HTTP-01 (standalone)"
    echo "  Note:     TCP 80 must be publicly reachable"
  else
    echo "  Method:   DNS-01 (Cloudflare)"
    echo "  Note:     Cloudflare token will be stored at $CF_CREDENTIALS_FILE (mode 600)"
  fi

  if ! prompt_yes_no "Proceed with installation"; then
    die "Aborted by user"
  fi
}

print_install_summary() {
  echo
  echo "--------------------------------------------------------------"
  echo -e "${C_BOLD}Install completed successfully${C_RESET}"
  echo "--------------------------------------------------------------"
  echo "Files created/updated:"
  echo "- $RENEW_HOOK"
  echo "- $CRON_FILE"
  if [ "$METHOD" = "dns-cloudflare" ]; then
    echo "- $CF_CREDENTIALS_FILE"
  fi
  echo "- $LOG_FILE"
  echo

  if [ "$METHOD" = "http" ]; then
    echo "Manual retry (HTTP-01):"
    echo "sudo certbot -n --agree-tos --standalone certonly -d \"$HOSTNAME\" -m \"$EMAIL\""
  else
    echo "Manual retry (DNS-01 Cloudflare):"
    echo "sudo certbot -n --agree-tos --dns-cloudflare --dns-cloudflare-credentials $CF_CREDENTIALS_FILE certonly -d \"$HOSTNAME\" -m \"$EMAIL\""
  fi

  echo
  echo "Optional renewal test:"
  echo "sudo certbot renew --force-renewal"
  echo
}

install_flow() {
  log "Starting install flow"

  collect_install_inputs

  step "Validating FileWave server prerequisites"
  ensure_filewave_server

  step "Validating DNS resolution"
  check_public_dns_resolution "$HOSTNAME"

  step "Backing up existing FileWave certs"
  backup_existing_certs

  step "Updating system packages"
  update_system_packages

  step "Ensuring certbot is installed"
  ensure_certbot_base

  if [ "$METHOD" = "dns-cloudflare" ]; then
    step "Preparing DNS-01 (Cloudflare) prerequisites"
    ensure_cloudflare_plugin
    setup_cloudflare_credentials

    step "Requesting certificate"
    request_certificate_dns_cloudflare
  else
    step "Requesting certificate"
    warn "HTTP-01 requires public reachability on TCP 80 during validation"
    request_certificate_http
  fi

  step "Applying certificate to FileWave"
  set_mdm_cert_trusted
  create_renew_hook
  inject_certificate_now

  step "Configuring auto-renewal"
  create_renew_cron

  print_install_summary
}

main() {
  local action="${1:-}"

  if [ "$action" != "--install" ] && [ "$action" != "--uninstall" ]; then
    usage
    exit 1
  fi

  require_root
  banner
  require_debian

  case "$action" in
    --install)
      install_flow
      ;;
    --uninstall)
      uninstall_files
      ;;
  esac
}

main "$@"
