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

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=/dev/null
source "$SCRIPT_DIR/libenv.sh"

ENV_FILE=$(limristem_mail_resolve_main_env_file)
BASE_DIR=${LIMRISTEM_MAIL_BASE_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}

usage() {
  cat <<'EOF'
Usage:
  manage-ssl.sh show [--json]
  manage-ssl.sh selfsigned [--json]
  manage-ssl.sh letsencrypt [email] [--json]
  manage-ssl.sh import <certificate.pem> <private-key.pem> [--json]
  manage-ssl.sh import-pem <certificate-base64> <private-key-base64> [--json]
EOF
}

load_env() {
  limristem_mail_load_env_file "$ENV_FILE"
}

json_flag() {
  if [[ "${1:-}" == "--json" || "${1:-}" == "yes" ]]; then
    printf 'yes\n'
  else
    printf 'no\n'
  fi
}

require_root() {
  if [[ $EUID -ne 0 ]]; then
    echo "Run as root." >&2
    exit 1
  fi
}

hostname_fqdn() {
  printf '%s\n' "${LIMRISTEM_MAIL_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
}

primary_domain() {
  local host
  host=$(hostname_fqdn)
  printf '%s\n' "${LIMRISTEM_MAIL_PRIMARY_DOMAIN:-$host}"
}

mta_sts_host() {
  printf '%s\n' "${LIMRISTEM_MAIL_MTA_STS_HOST:-mta-sts.$(primary_domain)}"
}

ssl_mode() {
  printf '%s\n' "${LIMRISTEM_MAIL_SSL_MODE:-selfsigned}"
}

current_cert_path() {
  local mode
  mode=$(ssl_mode)
  case "$mode" in
    manual)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_CERT_PATH:-/etc/ssl/limristem-mail/manual.crt}"
      ;;
    letsencrypt)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_CERT_PATH:-/etc/letsencrypt/live/$(hostname_fqdn)/fullchain.pem}"
      ;;
    plain)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_CERT_PATH:-}"
      ;;
    *)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_CERT_PATH:-/etc/ssl/limristem-mail/limristem-mail.crt}"
      ;;
  esac
}

current_key_path() {
  local mode
  mode=$(ssl_mode)
  case "$mode" in
    manual)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_KEY_PATH:-/etc/ssl/limristem-mail/manual.key}"
      ;;
    letsencrypt)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_KEY_PATH:-/etc/letsencrypt/live/$(hostname_fqdn)/privkey.pem}"
      ;;
    plain)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_KEY_PATH:-}"
      ;;
    *)
      printf '%s\n' "${LIMRISTEM_MAIL_TLS_KEY_PATH:-/etc/ssl/limristem-mail/limristem-mail.key}"
      ;;
  esac
}

le_email() {
  printf '%s\n' "${LIMRISTEM_MAIL_LE_EMAIL:-postmaster@$(primary_domain)}"
}

write_certbot_reload_hook() {
  mkdir -p /etc/letsencrypt/renewal-hooks/deploy
  cat > /etc/letsencrypt/renewal-hooks/deploy/limristem-mail-reload.sh <<'HOOK'
#!/usr/bin/env bash
set -euo pipefail
systemctl reload postfix >/dev/null 2>&1 || systemctl restart postfix >/dev/null 2>&1
systemctl reload dovecot >/dev/null 2>&1 || systemctl restart dovecot >/dev/null 2>&1
if systemctl list-unit-files nginx.service >/dev/null 2>&1; then
  systemctl reload nginx >/dev/null 2>&1 || systemctl restart nginx >/dev/null 2>&1
fi
HOOK
  chmod 750 /etc/letsencrypt/renewal-hooks/deploy/limristem-mail-reload.sh
}

reload_service_or_fail() {
  local service=$1
  systemctl reload "$service" >/dev/null 2>&1 || systemctl restart "$service" >/dev/null 2>&1 || {
    echo "Failed to reload or restart ${service} after applying SSL configuration." >&2
    exit 1
  }
}

render_nginx_file() {
  local template_file=$1
  local destination_file=$2
  local cert_path=$3
  local key_path=$4
  sed \
    -e "s|__TLS_CERT__|$cert_path|g" \
    -e "s|__TLS_KEY__|$key_path|g" \
    -e "s|__NGINX_SERVER_NAME__|$(hostname_fqdn)|g" \
    -e "s|__NGINX_ROOT_TARGET__|/panel|g" \
    -e "s|__API_INTERNAL_BIND__|${LIMRISTEM_MAIL_API_BIND:-127.0.0.1}|g" \
    -e "s|__API_INTERNAL_PORT__|${LIMRISTEM_MAIL_API_PORT:-8080}|g" \
    -e "s|__MTA_STS_HOST__|$(mta_sts_host)|g" \
    "$template_file" > "$destination_file"
}

apply_ssl_config() {
  local cert_path=$1
  local key_path=$2

  [[ -f "$cert_path" && -f "$key_path" ]] || {
    echo "Certificate or private key file is missing." >&2
    exit 1
  }
  validate_certificate_pair "$cert_path" "$key_path"

  if [[ -f "$BASE_DIR/templates/postfix/master.cf" ]]; then
    install -m 0644 "$BASE_DIR/templates/postfix/master.cf" /etc/postfix/master.cf
  fi
  postconf -e "smtpd_tls_cert_file=$cert_path"
  postconf -e "smtpd_tls_key_file=$key_path"
  postconf -e 'smtpd_tls_security_level = may'
  postconf -e 'smtpd_tls_auth_only = yes'
  postconf -e 'smtp_tls_security_level = may'
  postconf -X smtpd_use_tls || true
  postconf -X smtpd_tls_eecdh_grade || true
  postconf -e 'compatibility_level = 3.6'

  if [[ -f "$BASE_DIR/templates/dovecot/conf.d/10-master.conf" ]]; then
    install -m 0644 "$BASE_DIR/templates/dovecot/conf.d/10-master.conf" /etc/dovecot/conf.d/10-master.conf
  fi
  sed -i -E 's/^ssl = .*/ssl = required/' /etc/dovecot/dovecot.conf
  sed -i -E 's/^auth_allow_cleartext = .*/auth_allow_cleartext = no/' /etc/dovecot/conf.d/10-auth.conf
  cat > /etc/dovecot/conf.d/10-ssl-limristem-mail.conf <<CONF
ssl_server_cert_file = $cert_path
ssl_server_key_file = $key_path
CONF
  chmod 0644 /etc/dovecot/conf.d/10-ssl-limristem-mail.conf

  if [[ "${LIMRISTEM_MAIL_ENABLE_NGINX:-no}" == "yes" ]]; then
    mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
    if [[ -f "$BASE_DIR/templates/nginx/limristem-mail-api.conf" ]]; then
      render_nginx_file "$BASE_DIR/templates/nginx/limristem-mail-api.conf" /etc/nginx/sites-available/limristem-mail-api.conf "$cert_path" "$key_path"
      ln -sfn /etc/nginx/sites-available/limristem-mail-api.conf /etc/nginx/sites-enabled/limristem-mail-api.conf
    fi
    if [[ "${LIMRISTEM_MAIL_ENABLE_MTA_STS:-yes}" == "yes" && -f "$BASE_DIR/templates/nginx/limristem-mail-mta-sts.conf" ]]; then
      render_nginx_file "$BASE_DIR/templates/nginx/limristem-mail-mta-sts.conf" /etc/nginx/sites-available/limristem-mail-mta-sts.conf "$cert_path" "$key_path"
      ln -sfn /etc/nginx/sites-available/limristem-mail-mta-sts.conf /etc/nginx/sites-enabled/limristem-mail-mta-sts.conf
    fi
    nginx -t >/dev/null 2>&1 || {
      echo "Nginx configuration test failed after applying SSL configuration." >&2
      exit 1
    }
  fi

  reload_service_or_fail postfix
  reload_service_or_fail dovecot
  if [[ "${LIMRISTEM_MAIL_ENABLE_NGINX:-no}" == "yes" ]]; then
    reload_service_or_fail nginx
  fi
}

validate_certificate_pair() {
  local cert_file=$1
  local key_file=$2
  openssl x509 -in "$cert_file" -noout >/dev/null 2>&1 || {
    echo "Invalid PEM certificate file." >&2
    exit 1
  }
  openssl pkey -in "$key_file" -noout >/dev/null 2>&1 || {
    echo "Invalid PEM private key file." >&2
    exit 1
  }
  local cert_pub key_pub
  cert_pub=$(openssl x509 -in "$cert_file" -pubkey -noout 2>/dev/null | openssl pkey -pubin -outform pem 2>/dev/null)
  key_pub=$(openssl pkey -in "$key_file" -pubout -outform pem 2>/dev/null)
  if [[ "$cert_pub" != "$key_pub" ]]; then
    echo "Certificate and private key do not match." >&2
    exit 1
  fi
}

emit_show_payload() {
  local as_json=${1:-no}
  local mode cert_path key_path cert_exists key_exists status
  mode=$(ssl_mode)
  cert_path=$(current_cert_path)
  key_path=$(current_key_path)
  cert_exists=no
  key_exists=no
  [[ -n "$cert_path" && -f "$cert_path" ]] && cert_exists=yes
  [[ -n "$key_path" && -f "$key_path" ]] && key_exists=yes
  if [[ "$mode" == "plain" ]]; then
    status=plain
  elif [[ "$cert_exists" == "yes" && "$key_exists" == "yes" ]]; then
    status=ready
  else
    status=missing
  fi
  if [[ "$as_json" == "yes" ]]; then
    python3 - "$mode" "$(hostname_fqdn)" "$(le_email)" "$cert_path" "$key_path" "$cert_exists" "$key_exists" "$status" "${LIMRISTEM_MAIL_ENABLE_NGINX:-no}" "${LIMRISTEM_MAIL_ENABLE_MTA_STS:-yes}" <<'PY'
import json
import sys
payload = {
    "mode": sys.argv[1],
    "hostname": sys.argv[2],
    "le_email": sys.argv[3],
    "cert_path": sys.argv[4],
    "key_path": sys.argv[5],
    "cert_exists": sys.argv[6] == "yes",
    "key_exists": sys.argv[7] == "yes",
    "status": sys.argv[8],
    "nginx_enabled": sys.argv[9] == "yes",
    "mta_sts_enabled": sys.argv[10] == "yes",
}
print(json.dumps(payload))
PY
    return 0
  fi
  printf 'mode=%s\nhostname=%s\nle_email=%s\ncert_path=%s\nkey_path=%s\nstatus=%s\n' \
    "$mode" "$(hostname_fqdn)" "$(le_email)" "$cert_path" "$key_path" "$status"
}

emit_result_payload() {
  local action=$1
  local cert_path=$2
  local key_path=$3
  local as_json=${4:-no}
  if [[ "$as_json" == "yes" ]]; then
    python3 - "$action" "$(ssl_mode)" "$(hostname_fqdn)" "$cert_path" "$key_path" <<'PY'
import json
import sys
print(json.dumps({
    "ok": True,
    "action": sys.argv[1],
    "mode": sys.argv[2],
    "hostname": sys.argv[3],
    "cert_path": sys.argv[4],
    "key_path": sys.argv[5],
}))
PY
    return 0
  fi
  printf 'Applied %s certificate: %s\n' "$action" "$cert_path"
}

set_ssl_env() {
  local mode=$1
  local cert_path=$2
  local key_path=$3
  limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_SSL_MODE "$mode"
  limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_TLS_CERT_PATH "$cert_path"
  limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_TLS_KEY_PATH "$key_path"
}

prepare_managed_ssl_dir() {
  mkdir -p /etc/ssl/limristem-mail
  chmod 0755 /etc/ssl/limristem-mail
}

selfsigned_action() {
  local as_json=${1:-no}
  prepare_managed_ssl_dir
  local cert_path=/etc/ssl/limristem-mail/limristem-mail.crt
  local key_path=/etc/ssl/limristem-mail/limristem-mail.key
  # Keep self-signed certificates on a shorter rotation cadence: 397 days provides a small safety margin below the 398-day maximum validity period enforced by browsers and CAs.
  openssl req -x509 -nodes -newkey rsa:4096 -days 397 \
    -keyout "$key_path" \
    -out "$cert_path" \
    -subj "/CN=$(hostname_fqdn)" \
    -addext "subjectAltName=DNS:$(hostname_fqdn),DNS:$(mta_sts_host)"
  chmod 0600 "$key_path"
  chmod 0644 "$cert_path"
  validate_certificate_pair "$cert_path" "$key_path"
  set_ssl_env selfsigned "$cert_path" "$key_path"
  apply_ssl_config "$cert_path" "$key_path"
  emit_result_payload selfsigned "$cert_path" "$key_path" "$as_json"
}

letsencrypt_action() {
  local email_arg=${1:-}
  local as_json=${2:-no}
  local email_value=${email_arg:-$(le_email)}
  local host
  host=$(hostname_fqdn)
  local cert_domains=("-d" "$host")
  if [[ "${LIMRISTEM_MAIL_ENABLE_MTA_STS:-yes}" == "yes" && "$(mta_sts_host)" != "$host" ]]; then
    cert_domains+=("-d" "$(mta_sts_host)")
  fi
  certbot certonly --standalone \
    --non-interactive --agree-tos \
    --pre-hook 'systemctl stop nginx || true' \
    --post-hook 'systemctl start nginx || true' \
    "${cert_domains[@]}" -m "$email_value"
  local cert_path="/etc/letsencrypt/live/$host/fullchain.pem"
  local key_path="/etc/letsencrypt/live/$host/privkey.pem"
  [[ -f "$cert_path" && -f "$key_path" ]] || {
    echo "Let's Encrypt certificate was not created." >&2
    exit 1
  }
  limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_LE_EMAIL "$email_value"
  set_ssl_env letsencrypt "$cert_path" "$key_path"
  write_certbot_reload_hook
  systemctl enable --now certbot.timer >/dev/null 2>&1 || true
  apply_ssl_config "$cert_path" "$key_path"
  emit_result_payload letsencrypt "$cert_path" "$key_path" "$as_json"
}

import_action() {
  local source_cert=$1
  local source_key=$2
  local as_json
  as_json=$(json_flag "${3:-no}")
  validate_certificate_pair "$source_cert" "$source_key"
  prepare_managed_ssl_dir
  local cert_path=/etc/ssl/limristem-mail/manual.crt
  local key_path=/etc/ssl/limristem-mail/manual.key
  install -m 0644 "$source_cert" "$cert_path"
  install -m 0600 "$source_key" "$key_path"
  set_ssl_env manual "$cert_path" "$key_path"
  apply_ssl_config "$cert_path" "$key_path"
  emit_result_payload manual "$cert_path" "$key_path" "$as_json"
}

import_pem_action() {
  local cert_b64_file=$1
  local key_b64_file=$2
  local as_json
  as_json=$(json_flag "${3:-no}")
  local tmp_dir cert_file key_file previous_umask
  # Defensive: keep the temp directory and all decoded PEM artifacts private for the whole import scope.
  previous_umask=$(umask)
  umask 077
  tmp_dir=$(mktemp -d)
  trap 'umask "$previous_umask"; rm -rf "$tmp_dir"' RETURN
  cert_file="$tmp_dir/certificate.pem"
  key_file="$tmp_dir/private.key"
  if [[ -f "$cert_b64_file" ]]; then
    base64 -d < "$cert_b64_file" > "$cert_file"
  else
    printf '%s' "$cert_b64_file" | base64 -d > "$cert_file"
  fi
  if [[ -f "$key_b64_file" ]]; then
    base64 -d < "$key_b64_file" > "$key_file"
  else
    printf '%s' "$key_b64_file" | base64 -d > "$key_file"
  fi
  import_action "$cert_file" "$key_file" "$as_json"
}

load_env

command=${1:-}
case "$command" in
  show)
    emit_show_payload "$(json_flag "${2:-no}")"
    ;;
  selfsigned)
    require_root
    if [[ "${2:-}" == "--json" ]]; then
      selfsigned_action yes
    else
      selfsigned_action no
    fi
    ;;
  letsencrypt)
    require_root
    if [[ $# -ge 2 && "${2:-}" == "--json" ]]; then
      letsencrypt_action "" yes
    elif [[ $# -ge 3 && "${3:-}" == "--json" ]]; then
      letsencrypt_action "${2:-}" yes
    else
      letsencrypt_action "${2:-}" no
    fi
    ;;
  import)
    require_root
    import_action "${2:?certificate path required}" "${3:?key path required}" "${4:-no}"
    ;;
  import-pem)
    require_root
    import_pem_action "${2:?certificate base64 required}" "${3:?key base64 required}" "${4:-no}"
    ;;
  -h|--help|"")
    usage
    ;;
  *)
    usage >&2
    exit 1
    ;;
esac
